Khronos

Khronos Image (KMG) File Format Specification

Version 1.0.0 draft, 9 September 2015

This version:
https://www.khronos.org/opengles/sdk/tools/KMG/1.0/
Latest version:
https://www.khronos.org/opengles/sdk/tools/KMG/1.0/
Previous version:
https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
Editor:
Christophe Riccio (Unity)
Copyright © 2015 Khronos Group. See license.

Abstract

KMG is a standard graphics API independent container file format for storing textures used by GPUs.

Motivations

All these issues have been addressed by KMG file format.

Status of this document

Proposal draft.

Table of contents

File Structure

Byte[12] Identifier
UInt32 Endianness
UInt32 Target
UInt32 Format
UInt32 SwizzleRed
UInt32 SwizzleGreen
UInt32 SwizzleBlue
Uint32 SwizzleAlpha
UInt32 PixelWidth
UInt32 PixelHeight
UInt32 PixelDepth
UInt32 Layers
UInt32 Levels
UInt32 Faces
UInt32 GenerateMipmaps
UInt32 BaseLevel
UInt32 MaxLevel

for each Layer in Layers
  for each Level in Levels
     for each Face in Faces
         for each Depth in PixelDepth
             for each row or RowOfBlocks in PixelHeight
                 for each pixel or BlockOfPixels in PixelWidth
                     Byte data[format-specific-number-of-bytes]
                 end
             end
         end
     end
  end
end

Field Descriptions

Identifier

The file identifier is a unique set of bytes that will differentiate the file from other types of files. It consists of 12 bytes, as follows:

Byte[12] FileIdentifier = {
   0xAB, 0x4B, 0x4D, 0x4A, 0x31, 0x30, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
}

This can also be expressed using C-style character definitions as:

Byte[12] FileIdentifier = {
    '«', 'K', 'M', 'G', '1', '0', '0', '»', '\r', '\n', '\x1A', '\n'
}

The rationale behind the choice values in the identifier is based on the rationale for the identifier in the PNG specification. This identifier both identifies the file as a KMG file and provides for immediate detection of common file-transfer problems.

Endianness

Endianness contains the number 0x04030201 written as a 32 bit integer. If the file is little endian then this is represented as the bytes 0x01 0x02 0x03 0x04. If the file is big endian then this is represented as the bytes 0x04 0x03 0x02 0x01. When reading endianness as a 32 bit integer produces the value 0x04030201 then the endianness of the file matches the endianness of the program that is reading the file and no conversion is necessary. When reading endianness as a 32 bit integer produces the value 0x01020304 then the endianness of the file is opposite the endianness of the program that is reading the file, and in that case the program reading the file must endian convert all header bytes to the endianness of the program (i.e. a little endian program must convert from big endian, and a big endian program must convert to little endian).

Target

Target must be one of the following values:

NameValueDescription
TARGET_1D0Images in this texture all are 1-dimensional. They have width, but no height or depth
TARGET_1D_ARRAY1Images in this texture all are 1-dimensional. However, it contains multiple sets of 1-dimensional images, all within one texture. The array length is part of the texture's size.
TARGET_2D2Images in this texture all are 1-dimensional. However, it contains multiple sets of 1-dimensional images, all within one texture. The array length is part of the texture's size.
TARGET_2D_ARRAY3Images in this texture all are 1-dimensional. However, it contains multiple sets of 1-dimensional images, all within one texture. The array length is part of the texture's size.
TARGET_3D4Images in this texture all are 3-dimensional. They have width, height, and depth.
TARGET_BUFFER5The image in this texture (only one image. No mipmapping) is 1-dimensional. The storage for this data comes from a Buffer Object.
TARGET_CUBE6There are exactly 6 distinct sets of 2D images, all of the same size. They act as 6 faces of a cube.
TARGET_CUBE_ARRAY7Images in this texture are all cube maps. It contains multiple sets of cube maps, all within one texture. The array length * 6 (number of cube faces) is part of the texture size.
TARGET_RECTANGLE8The image in this texture (only one image. No mipmapping) is 2-dimensional. Texture coordinates used for these textures are not normalized.

Format

Format must be one of the following values:

NameValueCommentReference
FORMAT_RG4_UNORM1
FORMAT_RG4_USCALED2
FORMAT_RGBA4_UNORM3
FORMAT_RGBA4_USCALED4
FORMAT_R5G6B5_UNORM5
FORMAT_R5G6B5_USCALED6
FORMAT_RGB5A1_UNORM7
FORMAT_RGB5A1_USCALED8
FORMAT_R8_UNORM9
FORMAT_R8_SNORM10
FORMAT_R8_USCALED11
FORMAT_R8_SSCALED12
FORMAT_R8_UINT13
FORMAT_R8_SINT14
FORMAT_R8_SRGB15
FORMAT_RG8_UNORM16
FORMAT_RG8_SNORM17
FORMAT_RG8_USCALED18
FORMAT_R8_SSCALED19
FORMAT_RG8_UINT20
FORMAT_RG8_SINT21
FORMAT_RG8_SRGB22
FORMAT_RGB8_UNORM23
FORMAT_RGB8_SNORM24
FORMAT_RGB8_USCALED25
FORMAT_RGB8_SSCALED26
FORMAT_RGB8_UINT27
FORMAT_RGB8_SINT28
FORMAT_RGB8_SRGB29
FORMAT_RGBA8_UNORM30
FORMAT_RGBA8_SNORM31
FORMAT_RGBA8_USCALED32
FORMAT_RGBA8_SSCALED33
FORMAT_RGBA8_UINT34
FORMAT_RGBA8_SINT35
FORMAT_RGBA8_SRGB36
FORMAT_RGB10A2_UNORM37
FORMAT_RGB10A2_SNORM38
FORMAT_RGB10A2_USCALED39
FORMAT_RGB10A2_SSCALED40
FORMAT_RGB10A2_UINT41
FORMAT_RGB10A2_SINT42
FORMAT_R16_UNORM43
FORMAT_R16_SNORM44
FORMAT_R16_USCALED45
FORMAT_R16_SSCALED46
FORMAT_R16_UINT47
FORMAT_R16_SINT48
FORMAT_R16_SFLOAT49
FORMAT_RG16_UNORM50
FORMAT_RG16_SNORM51
FORMAT_RG16_USCALED52
FORMAT_RG16_SSCALED53
FORMAT_RG16_UINT54
FORMAT_RG16_SINT55
FORMAT_RG16_SFLOAT56
FORMAT_RGB16_UNORM57
FORMAT_RGB16_SNORM58
FORMAT_RGB16_USCALED59
FORMAT_RGB16_SSCALED60
FORMAT_RGB16_UINT61
FORMAT_RGB16_SINT62
FORMAT_RGB16_SFLOAT63
FORMAT_RGBA16_UNORM64
FORMAT_RGBA16_SNORM65
FORMAT_RGBA16_USCALED66
FORMAT_RGBA16_SSCALED67
FORMAT_RGBA16_UINT68
FORMAT_RGBA16_SINT69
FORMAT_RGBA16_SFLOAT70
FORMAT_R32_UINT71
FORMAT_R32_SINT72
FORMAT_R32_SFLOAT73
FORMAT_RG32_UINT74
FORMAT_RG32_SINT75
FORMAT_RG32_SFLOAT76
FORMAT_RGB32_UINT77
FORMAT_RGB32_SINT78
FORMAT_RGB32_SFLOAT79
FORMAT_RGBA32_UINT80
FORMAT_RGBA32_SINT81
FORMAT_RGBA32_SFLOAT82
FORMAT_R64_SFLOAT83
FORMAT_RG64_SFLOAT84
FORMAT_RGB64_SFLOAT85
FORMAT_RGBA64_SFLOAT86
FORMAT_R64_SFLOAT83
FORMAT_RG64_SFLOAT84
FORMAT_RGB64_SFLOAT85
FORMAT_RGBA64_SFLOAT86
FORMAT_RG11B10_UFLOAT87
FORMAT_RGB9E5_UFLOAT88
FORMAT_D16_UNORM89
FORMAT_D24_UNORM90
FORMAT_D32_SFLOAT91
FORMAT_S8_UINT92
FORMAT_D16_UNORM_S8_UINT93
FORMAT_D24_UNORM_S8_UINT94
FORMAT_D32_SFLOAT_S8_UINT95
FORMAT_RGB_DXT1_UNORM96[S3TC]
FORMAT_RGB_DXT1_SRGB97[S3TC_SRGB]
FORMAT_RGBA_DXT1_UNORM98[S3TC]
FORMAT_RGBA_DXT1_SRGB99[S3TC_SRGB]
FORMAT_RGBA_DXT3_UNORM100[S3TC]
FORMAT_RGBA_DXT3_SRGB101[S3TC_SRGB]
FORMAT_RGBA_DXT5_UNORM102[S3TC]
FORMAT_RGBA_DXT5_SRGB103[S3TC_SRGB]
FORMAT_R_ATI1N_UNORM104[RGTC]
FORMAT_R_ATI1N_SNORM105[RGTC]
FORMAT_RG_ATI2N_UNORM106[RGTC]
FORMAT_RG_ATI2N_SNORM107[RGTC]
FORMAT_RGB_BP_UFLOAT108[BPTC]
FORMAT_RGB_BP_SFLOAT109[BPTC]
FORMAT_RGBA_BP_UNORM110[BPTC]
FORMAT_RGBA_BP_SRGB111[BPTC]
FORMAT_RGB_ETC2_UNORM112[ETC2]
FORMAT_RGB_ETC2_SRGB113[ETC2]
FORMAT_RGBA_ETC2_A1_UNORM114[ETC2]
FORMAT_RGBA_ETC2_A1_SRGB115[ETC2]
FORMAT_RGBA_ETC2_UNORM116[ETC2]
FORMAT_RGBA_ETC2_SRGB117[ETC2]
FORMAT_R_EAC_UNORM118[ETC2]
FORMAT_R_EAC_SNORM119[ETC2]
FORMAT_RG_EAC_UNORM120[ETC2]
FORMAT_RG_EAC_SNORM121[ETC2]
FORMAT_RGBA_ASTC_4X4_UNORM122[ASTC]
FORMAT_RGBA_ASTC_4X4_SRGB123[ASTC]
FORMAT_RGBA_ASTC_5X4_UNORM124[ASTC]
FORMAT_RGBA_ASTC_5X4_SRGB125[ASTC]
FORMAT_RGBA_ASTC_5X5_UNORM126[ASTC]
FORMAT_RGBA_ASTC_5X5_SRGB127[ASTC]
FORMAT_RGBA_ASTC_6X5_UNORM128[ASTC]
FORMAT_RGBA_ASTC_6X5_SRGB129[ASTC]
FORMAT_RGBA_ASTC_6X6_UNORM130[ASTC]
FORMAT_RGBA_ASTC_6X6_SRGB131[ASTC]
FORMAT_RGBA_ASTC_8X5_UNORM132[ASTC]
FORMAT_RGBA_ASTC_8X5_SRGB133[ASTC]
FORMAT_RGBA_ASTC_8X6_UNORM134[ASTC]
FORMAT_RGBA_ASTC_8X6_SRGB135[ASTC]
FORMAT_RGBA_ASTC_8X8_UNORM136[ASTC]
FORMAT_RGBA_ASTC_8X8_SRGB137[ASTC]
FORMAT_RGBA_ASTC_10X5_UNORM138[ASTC]
FORMAT_RGBA_ASTC_10X5_SRGB139[ASTC]
FORMAT_RGBA_ASTC_10X6_UNORM140[ASTC]
FORMAT_RGBA_ASTC_10X6_SRGB141[ASTC]
FORMAT_RGBA_ASTC_10X8_UNORM142[ASTC]
FORMAT_RGBA_ASTC_10X8_SRGB143[ASTC]
FORMAT_RGBA_ASTC_10X10_UNORM144[ASTC]
FORMAT_RGBA_ASTC_10X10_SRGB145[ASTC]
FORMAT_RGBA_ASTC_12X10_UNORM146[ASTC]
FORMAT_RGBA_ASTC_12X10_SRGB147[ASTC]
FORMAT_RGBA_ASTC_12X12_UNORM148[ASTC]
FORMAT_RGBA_ASTC_12X12_SRGB149[ASTC]
FORMAT_BGRA4_UNORM150
FORMAT_BGRA4_USCALED151
FORMAT_B5G6R5_UNORM152
FORMAT_B5G6R5_USCALED153
FORMAT_BGR5A1_UNORM154
FORMAT_BGR5A1_USCALED155
FORMAT_BGR8_UNORM156
FORMAT_BGR8_SNORM157
FORMAT_BGR8_USCALED158
FORMAT_BGR8_SSCALED159
FORMAT_BGR8_UINT160
FORMAT_BGR8_SINT161
FORMAT_BGR8_SRGB162
FORMAT_BGRA8_UNORM163
FORMAT_BGRA8_SNORM164
FORMAT_BGRA8_USCALED165
FORMAT_BGRA8_SSCALED166
FORMAT_BGRA8_UINT167
FORMAT_BGRA8_SINT168
FORMAT_BGRA8_SRGB169
FORMAT_BGR10A2_UNORM170
FORMAT_BGR10A2_SNORM171
FORMAT_BGR10A2_USCALED172
FORMAT_BGR10A2_SSCALED173
FORMAT_BGR10A2_UINT174
FORMAT_BGR10A2_SINT175
FORMAT_RG3B2_UNORM176
FORMAT_BGRX8_UNORM177
FORMAT_BGRX8_SRGB178
FORMAT_L8_UNORM179
FORMAT_A8_UNORM180
FORMAT_LA8_UNORM181
FORMAT_L16_UNORM182
FORMAT_A16_UNORM183
FORMAT_LA16_UNORM184
FORMAT_RGB_PVRTC1_8X8_UNORM1854 bits per pixel format [PVRTC1]
FORMAT_RGB_PVRTC1_8X8_SRGB1864 bits per pixel format [PVRTC1_SRGB]
FORMAT_RGB_PVRTC1_16X8_UNORM1872 bits per pixel format [PVRTC1]
FORMAT_RGB_PVRTC1_16X8_SRGB1882 bits per pixel format [PVRTC1_SRGB]
FORMAT_RGBA_PVRTC1_8X8_UNORM1894 bits per pixel format [PVRTC1]
FORMAT_RGBA_PVRTC1_8X8_SRGB1904 bits per pixel format [PVRTC1_SRGB]
FORMAT_RGBA_PVRTC1_16X8_UNORM1912 bits per pixel format [PVRTC1]
FORMAT_RGBA_PVRTC1_16X8_SRGB1922 bits per pixel format [PVRTC1_SRGB]
FORMAT_RGBA_PVRTC2_4X4_UNORM1934 bits per pixel format [PVRTC2]
FORMAT_RGBA_PVRTC2_4X4_SRGB1944 bits per pixel format [PVRTC2_SRGB]
FORMAT_RGBA_PVRTC2_8X4_UNORM1952 bits per pixel format [PVRTC2]
FORMAT_RGBA_PVRTC2_8X4_SRGB1962 bits per pixel format [PVRTC2_SRGB]
FORMAT_RGB_ETC_UNORM197 [ETC1]
FORMAT_RGB_ATC_UNORM198 [ATC]
FORMAT_RGBA_ATC_EXPLICIT_UNORM199 [ATC]
FORMAT_RGBA_ATC_INTERPOLATED_UNORM200 [ATC]

The per block storage for compressed formats is described in reference documents.

The channels exposed by the formats are exposed using the following letters:

The order of the components in the formats follows the order of the component in memory.

The following postfixs are used to describ properties of the format:

SwizzleRed, SwizzleGreen, SwizzleBlue, SwizzleAlpha

Swizzle is a mechanism to swizzle the components of a texture before they are applied as they are returned to the shader. SwizzleRed, SwizzleGreen, SwizzleBlue and SwizzleAlpha must take one of the following values:

NameValue
SWIZZLE_RED0
SWIZZLE_GREEN1
SWIZZLE_BLUE2
SWIZZLE_ALPHA3
SWIZZLE_ZERO4
SWIZZLE_ONE5

The first swizzle parameter affects the first component of Cs as:

    if (SwizzleRed == SWIZZLE_RED) {
        TexelDestination[0] = TexelSource[0];
    } else if (SwizzleRed == SWIZZLE_GREEN) {
        TexelDestination[0] = TexelSource[1];
    } else if (SwizzleRed == SWIZZLE_BLUE) {
        TexelDestination[0] = TexelSource[2];
    } else if (SwizzleRed == SWIZZLE_ALPHA) {
        TexelDestination[0] = TexelSource[3];
    } else if (SwizzleRed == SWIZZLE_ZERO) {
        TexelDestination[0] = 0;
    } else if (SwizzleRed == SWIZZLE_ONE) {
        TexelDestination[0] = 1; // float or int depending on texture component type
    }

    and similarly for the other components.

PixelWidth, PixelHeight, PixelDepth

The size of the texture image for level 0, in pixels. No rounding to block sizes should be applied for block compressed textures.

For 1D textures PixelHeight and PixelDepth must be 1. For 2D and cube textures PixelDepth must be 1.

Layers

Layers specifies the number of layers in a texture array. The minimum value is 1.

Levels

Levels must equal 1 for non-mipmapped textures. For mipmapped textures, it equals the number of mipmaps. Mipmaps are stored in order from largest size to smallest size. The first mipmap level is always level 0. A KMG file does not need to contain a complete mipmap pyramid. If Levels equals 0, it indicates that a full mipmap pyramid should be generated from level 0 at load time (this is usually not allowed for compressed formats).

Faces

Faces specifies the number of cubemap faces. For cubemaps and cubemap arrays this should be 6. For non cubemaps this should be 1. Cube map faces are stored in the order: +X, -X, +Y, -Y, +Z, -Z.

GenerateMipmaps

Mipmap generation replaces texel image levels baselevel + 1 through q with images derived from the baselevel image, regardless of their previous contents. All other mimap images, including the baselevel + 1 image, are left unchanged by this computation.

The internal formats of the derived mipmap images all match those of the baselevel image. The contents of the derived images are computed by repeated, filtered reduction of the baselevel + 1 image. For one- and two-dimensional array and cube map array textures, each layer is filtered independently.

NameValue
FILTER_NONE0
FILTER_NEAREST1
FILTER_LINEAR2

If the texture format is compressed, GenerateMipmaps must be FILTER_NONE.

BaseLevel

Specifies the index of the lowest defined mipmap level. The minimum value is 0. The maximum value is log2(max(width, height, depth)) + 1. BaseLevel must be lower or equal to MaxLevel.

MaxLevel

Sets the index of the highest defined mipmap level. This is an integer value. The minimum value is 0. The maximum value is log2(max(width, height, depth)) + 1. BaseLevel must be lower or equal to MaxLevel.

General comments

The unpack alignment is 4. I.e. uncompressed pixel data is packed according to the rules described in section 8.4.4.1 of the OpenGL 4.4 specification [OPENGL44] for a GL_UNPACK_ALIGNMENT of 4.

Values listed in tables referred to in the OpenGL 4.4 specification [OPENGL44] may be supplemented by extensions. The references are given as examples and do not imply that all of those texture types can be loaded in OpenGL ES or earlier versions of OpenGL.

Texture data in a KMG file are arranged so that the first pixel in the data stream for each face and/or array element is closest to the origin of the texture coordinate system. In OpenGL that origin is conventionally described as being at the lower left, but this convention is not shared by all image file formats and content creation tools, so there is abundant room for confusion.

The desired texture axis orientation is often predetermined by, e.g. a content creation tool's or existing application's use of the image. Therefore it is strongly recommended that tools for generating KMG files clearly describe their behaviour, and provide an option to specify the texture axis origin and orientation relative to the logical orientation of the source image. At minimum they should provide a choice between top-left and bottom-left as origin for 2D source images, with the positive S axis pointing right. Where possible, the preferred default is to use the logical upper-left corner of the image as the texture origin. Note that this is contrary to the standard interpretation of GL texture coordinates. However, the majority of texture compression tools use this convention.

As an aid to writing image manipulation tools and viewers, the logical orientation of the data in a KMG file may be indicated in the file's key/value metadata. Note that this metadata affects only the logical interpretation of the data, has no effect on the mapping from pixels in the file byte stream to texture coordinates. The recommended key to use is:

It is recommended that viewing and editing tools support at least the following values:

where

Although other orientations can be represented, it is recommended that tools that create KMG files use only the values listed above as other values may not be widely supported by other tools.

An example KMG 1.0 file:

// HEADER
0xAB, 0x4B, 0x54, 0x58, // first four bytes of Byte[12] identifier
0x20, 0x32, 0x30, 0xBB, // next four bytes of Byte[12] identifier
0x0D, 0x0A, 0x1A, 0x0A. // final four bytes of Byte[12] identifier
0x04, 0x03, 0x02, 0x01, // Byte[4] endianness (Big endian in this case)
0x00, 0x00, 0x00, 0x02, // UInt32 Target = TARGET_2D
0x00, 0x00, 0x00, 0x24, // UInt32 Format = FORMAT_RGBA8_SRGB
0x00, 0x00, 0x00, 0x00, // UInt32 SwizzleRed = SWIZZLE_RED
0x00, 0x00, 0x8D, 0x64, // UInt32 SwizzleGreen = SWIZZLE_GREEN
0x00, 0x00, 0x19, 0x07, // UInt32 SwizzleBlue = SWIZZLE_BLUE
0x00, 0x00, 0x19, 0x07, // UInt32 SwizzleAlpha = SWIZZLE_ALPHA
0x00, 0x00, 0x00, 0x20, // UInt32 PixelWidth = 32
0x00, 0x00, 0x00, 0x20, // UInt32 PixelHeight = 32
0x00, 0x00, 0x00, 0x00, // UInt32 PixelDepth = 1
0x00, 0x00, 0x00, 0x00, // UInt32 Layers = 1
0x00, 0x00, 0x00, 0x01, // UInt32 Faces = 1
0x00, 0x00, 0x00, 0x01, // UInt32 Levels = 1
0x00, 0x00, 0x00, 0x00, // UInt32 GenerateMipmaps = FILTER_NONE
// TEXTURE DATA
0xD8, 0xD8, 0xD8, 0xDA, // Byte[512] RGBA8 texture data...
...

IANA Mime-Type Registration Information

Type name: Image

Subtype name: kmg

Required parameters: none

Optional parameters: none

Encoding considerations: binary

Security considerations:

The kmg type is a binary data stream which contains no executable code that could disrupt a client processor. There is no provision in the type specification that would allow authors to insert executable code that would present any security risk to a client machine.

Because every item's length is available at its beginning, there is robust defense against corrupted or fraudulent data that might overflow a decoder's buffer. Also the signature bytes provide early detection of common file transmission errors.

The kmg type may contain texture data compressed using OpenGL standard or vendor-specific schemes. These compression schemes are designed so small blocks of data (typically around 64 bits) can be decompressed in real time into a small block of pixels (typically 4x4) during texel fetch. In such schemes it is not possible for a small amount of data to expand enormously because the level of compression is limited; the compressed size is related directly to the number of pixels in the uncompressed image and not to the content of the data.

The kmg type does not provide encryption of the data payload. Users or applications wishing or needing to keep their images confidential must overlay their own encryption on the kmg data during transmission.

Interoperability considerations:

The kmg type includes a field identifying the endianness of the machine which created the data. Applications reading the data are expected to check this field and convert the endianness, if necessary. The texture data payload may be compressed using an OpenGL-vendor-specific scheme. In this case, only devices or applications having a matching decompressor will be able to display the data. The compression scheme is identified in the kmg data so applications can quickly reject data using unsupported schemes.

References

[S3TC]
GL_EXT_texture_compression_s3tc, Pat Brown, Slawomir Grajewski, July 2013.
[S3TC_SRGB]
GL_EXT_texture_sRGB, Mark J. Kilgard, January 2007.
[RGTC]
GL_ARB_texture_compression_rgtc, Mark J. Kilgard, January 2015.
[BPTC]
GL_ARB_texture_compression_bptc, Eric Werness, Piers Daniell, January 2011.
[ETC1]
GL_OES_compressed_ETC1_RGB8_texture, Jacob Strom, April 2008.
[ETC2]
GL_ARB_ES3_compatibility, Piers Daniell, October 2013.
[ASTC]
GL_KHR_texture_compression_astc_hdr, Sean Ellis, Jon Leech, September 2012.
[PVRTC1]
GL_IMG_texture_compression_pvrtc, Graham Connor, June 2012.
[PVRTC2]
GL_IMG_texture_compression_pvrtc2, Ben Bowman, December 2012.
[PVRTC_SRGB]
GL_EXT_pvrtc_sRGB, Benj Lipchak, June 2013.
[ATC]
GL_AMD_compressed_ATC_texture, Maurice Ribble, February 2008.

Acknowledgements

Revision History

2015-09-09
Initial draft

License