Sign in Join
Wiki copytemporary

LazPaint LZP Format: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## LZP File format specification
22
33In this document, [[Hexadecimal|hexadecimal]] numbers follow Pascal convention, i.e. they are prefixed with a [[Dollar sign|dollar sign]]. For example \$10 means 16. As an exception, sequences of bytes are not prefixed.
44
55The default encoding for numbers is little endian. So the hexadecimal number \$12345678 would be stored as the sequence of bytes 78 56 34 12.
66
77Numbers are 32-bit signed integers, unless stated otherwise. So -1 is stored like \$FFFFFFFF.
88
99### Global structure
1010
1111The file is composed of:
1212
1313`Offset  Content`\
1414`------  --------------------`\
1515`0       File header`\
1616`?       Thumbnail (optional)`\
1717`?       `[[LazPaint LZP Format Single Image|Flat image]]\
1818`?       Layer structure (optional)`
1919
2020Note: some old formats do not have a header and start directly with the flat image. Image readers may discard those files. However for your information, in this case, images are always stored with ZStream compression and the size of the image is deduced from the size of the flat image.
2121
2222### File header
2323
2424`Offset  Size  Name                Content`\
2525`------  ----  ------------------  ------------------------`\
2626`0       8     `*`magic`*`               Always contains the string 'LazPaint'`\
2727`8       4     `*`zero1`*`               Contains the number 0`\
2828`12      4     `*`headerSize`*`          Size of this header`\
29-\
29+
3030`16      4     `*`width`*`               Width of the image canvas`\
3131`20      4     `*`height`*`              Height of the image canvas`\
3232`24      4     `*`nbLayers`*`            Number of layers`\
3333`28      4     `*`previewOffset`*`       Offset (from the beginning of the file) of the `[[LazPaint LZP Format Single Image|flattened image]]\
34-\
34+
3535`32      4     `*`zero2`*`               Contains the number 0`\
3636`36      4     `*`compressionMode`*`     Contains flags indicating the compression and the presence of a thumbnail`\
3737`40      4     `*`reserved1`*`           Reserved for future use. Set to 0.`\
3838`44      4     `*`layersOffset`*`        Offset (from the beginning of the file) of the layer structure`
3939
4040*headerSize* contains the size of this header. An image reader must read this to know which fields are available. It must be greater or equal to 48.
4141
4242*width* and *height* are the dimension of the canvas. There is no real limitation for these numbers however it is recommended to keep it under or equal to 8192.
4343
4444*nbLayers* is the number of layers. The minimum is 1. There is no real limitation however it is recommended to keep it under or equal to 99.
4545
4646*layersOffset* can be 0, in which case the image is flat and that there is no layer description.
4747
4848#### Compression
4949
5050*compressionMode* contains flags indicating the compression and the presence of a thumbnail.
5151
5252The low order byte indicates the compression of the flattened image. It is the value (*compressionMode* **and** \$ff):
5353
5454\- \$01 indicates ZStream compression
5555
5656\- \$02 indicates RLE compression
5757
5858\- any other value should be considered as an indication that the image reader cannot interpret the content of the file
5959
6060The following flags can be defined:
6161
6262\- \$100 indicates that there is thumbnail in PNG format; check with (*compressionMode* **and** \$100) \<\> 0
6363
6464If there is a thumbnail, it is directly after the header. The reader should read *headerSize* to have the correct location.
6565
6666The compression of the layers is defined in the layer structure.
6767
6868#### Thumbnail
6969
7070Its presence is recommended however optional. Its presence is specified by the flag \$100 of *compressionMode*.
7171
7272Currently, it is a PNG file embedded directly after the header. It is provided as a way to facilitate quick preview.
7373
7474PNG format is widespread so it is easy to implement a browser that would display the thumbnail. To do so, check that the header contains *magic* and that the flag for the thumbnail is set in *compressionMode*. Then jump to the end of the header using *headerSize*. Then read the PNG file.
7575
7676There is no real limitation but the recommended size for the thumbnail is 128 along the largest dimension. The other dimension may be smaller if the image is not a square. Of course if the image is smaller than 128 then the thumbnail will be smaller than 128.
7777
7878### Flat image
7979
8080The file always include a flattened image to make it easier for image viewers. It is in the [[LazPaint LZP Format Single Image|LZP single image format]].
8181
8282If there is no layer structure, the caption of this image is the name of the unique layer. If there is a layer structure, then the caption of this image is discarded.
8383
8484### Layer structure
8585
8686`Offset  Size  Content`\
8787`------  ----  -------------------------------------------------------`\
8888`0       20    Contains the string 'TBGRALayeredBitmap' followed by the bytes 1A 00`\
8989`20      4     Size S of the additional header after this field (must be at least 12)`\
9090`24      4     Number of layers N (should be identical to the number in the file header)`\
9191`28      4     Selected layer index. Equals to -1 if no layer is selected, otherwise positive from 0 to N-1`\
9292`32      4     Stack flags`\
9393`36      4     Canvas width (optional)`\
9494`40      4     Canvas height (optional)`\
9595`44      8     64-bit offset to the directory structure relative to the start of this header (optional)`\
9696`24+S          First layer (bottom one)`
9797
9898The following flags are defined:
9999
100100- \$01: the layers are blended without gamma correction
101101- \$02: the layers are compressed with RLE compression (otherwise they are compressed with ZStream compression)
102102
103103If the canvas size is not supplied (when the total header size is less than 44), then the canvas size is the size of the first non-empty layer bitmap.
104104
105105Similarly, the [[LazPaint LZP directory structure|directory structure]] offset may not to be supplied or set to zero. In this case, there is none. It is optional and only necessary for vector shapes.
106106
107107The total header size of the layer structure is equal to 24 + S. Then for each layer there is a layer entry.
108108
109109The first layer entry is the bottom most layer. Then each layer is blended over the previous ones. The last layer is on top and is not hidden by any other layer.
110110
111111#### Layer entry
112112
113113Header of the layer entry:
114114
115115`Offset  Size  Content`\
116116`------  ----  -------------------------------------------------------`\
117117`0       4     Size S1 of the optional fields that follows`\
118118`4       4     Layer flags (lowest bit means that it is visible)`\
119119`8       4     Layer blend operation`\
120120`12      4     Layer horizontal offset in pixels`\
121121`16      4     Layer vertical offset in pixels`\
122122`20      4     Layer ID (if used, each layer must have a different ID in a given file)`\
123123`24      4     Layer opacity from 0 (transparent) to 65535 (opaque)`\
124124`28      4     Lower bits of size S2 of image data`\
125125`32      4     Upper bits of size S2 (optional, forming 64-bit integer)`\
126126`36      16    GUID of the original used to render the layer (optional)`\
127127`52      24    Affine matrix of the transform applied to the original`\
128128`4+S1    S2    `[[LazPaint LZP Format Single Image|Image data]]\
129129`4+S1+S2       Next entry`
130130
131131All fields are optional so in theory, S1 can be set to zero and the image data can directly follow. However it is recommended to write the whole header.
132132
133133The default values are: visible, blend operation boTransparent, offset (0,0), undefined ID (maximum integer value), opaque, size S2 = 0, GUID = NULL. If an original is specified the affine matrix transform is mandatory.
134134
135135The size of the image data is a 64-bit integer, though the upper value is optional and by default zero. When the image size S2 \<\> 0, the offset to the next entry is computed using the formula 4+S1+S2. Otherwise, the data is expected to continue after the end of the image data.
136136
137137The GUID is stored here as binary (in little endian): one DWord, two Words and 8 bytes.
138138
139139The affine transform is stored as binary (in little endian): 6 single precision floats ux, uy, vx, vy, tx, ty (axes u and v and translation t).
140140
141141#### Blend operations
142142
143143- Under is the value of the channels of the layer underneath
144144- Over is the value of the channels of the layer above
145145- Mix is an intermediate value that is then blended over the previous layers, and that, unless specified, is computed for each RGB channel. The alpha channel is computed differently (see below table).
146146- Final is the resulting value
147147
148148Note:
149149
150150- values (Red, Green, Blue, Alpha, Mix, Under, Over, Final) are considered to be between 0 and 255
151151- GE means gamma expansion. GE(x) = Power(x/255, 2.1)*65535
152152- GC means gamma compression. GC(x) = Power(x/65535, 1/2.1)*255
153153- ByteSqrt means the square root. ByteSqrt(x) = Power(X/255, 0.5)*255
154154- **div** stands for integer division (rounded down)
155155- HSL is the standard HSL colorspace, without gamma correction
156156- GSB is the corrected HSL colorspace of LazPaint. It is computed with gamma correction and the B (brightness) component correspond to the perceived lightness:
157157
158158`B = (306*GE(Red) + 601*GE(Green) + 117*GE(Blue))/1024`\
159159`B between 0 and 65535`
160160
161161Here is the list of operations supported in LazPaint:
162162
163163`Value  Name               Description`\
164164`-----  -----------------  ----------------------------------------------------`\
165165`0      LinearBlend        Blend over the previous layers without gamma correction`\
166166`1      Transparent        Blend over the previous layers `\
167167`                          (with gamma correction unless it is specified otherwise in the stack flags)`\
168-\
168+
169169`2      Lighten            Mix := Max(Over, Under)`\
170170`3      Screen             Mix := 255 - ((255 - Under) * (255 - Over) `**`shr`**` 8)`\
171171`4      Additive           Mix := GC(GE(Under)+GE(Over))`\
172172`5      LinearAdd          Mix := Under + Over`\
173173`6      ColorDodge         Mix := `**`if`**` Over = 255 `**`then`**` 255`\
174174`                                 `**`else`**` Min(255, (Under `**`shl`**` 8) `**`div`**` (255 - Over))`\
175175`7      Divide             Mix := `**`if`**` Over = 0 `**`then`**` 255`\
176176`                                 `**`else`**` Min(255, (Under `**`shl`**` 8) `**`div`**` Over)`\
177177`8      NiceGlow           Mix := `**`if`**` Under = 255 `**`then`**` 255`\
178178`                                 `**`else`**` Min(255, Over*Over `**`div`**` (255 - Under))`\
179179`9      SoftLight          Mix := ((255 - Under)*Over shr 7 + Under)*Under div 255`\
180180`10     HardLight          Mix := `**`if`**` Over <= 128 `**`then`**` Under*Over `**`shr`**` 7`\
181181`                                 `**`else`**` 255 - ((255 - Under)*(255 - Over) `**`shr`**` 7)`\
182-\
182+
183183`11     Glow               Mix := `**`if`**` Under = 255 `**`then`**` 255`\
184184`                                 `**`else`**` Min(255, Over*Over `**`div`**` (255 - Under))`\
185185`12     Reflect            Mix := `**`if`**` Over = 255 `**`then`**` 255`\
186186`                                 `**`else`**` Min(255, Under*Under `**`div`**` (255 - Over))`\
187187`13     Overlay            Mix := `**`if`**` Under <= 128 `**`then`**` Under*Over `**`shr`**` 7`\
188188`                                 `**`else`**` 255 - ((255 - Under)*(255 - Over) `**`shr`**` 7)`\
189189`14     DarkOverlay        Mix := `**`if`**` GE(Under) <= 32768 `**`then`**` Under*Over `**`shr`**` 7`\
190190`                                 `**`else`**` GC(65535 - ((65535 - GE(Under))*(65535 - GE(Over)) `**`shr`**` 15)`\
191191`15     Darken             Mix := Min(Under, Over)`\
192192`16     Multiply           Mix := `**`if`**` Over >= 128 `**`then`**` Under*(Over+1) `**`shr`**` 8 `**`else`**` Under*Over `**`shr`**` 8`\
193193`17     ColorBurn          Mix := `**`if`**` Over = 0 `**`then`**` 0`\
194194`                                 `**`else`**` Max(0, 255 - (Under `**`shl`**` 8) `**`div`**` Over)`\
195-\
195+
196196`18     Difference         Mix := GC(Abs(GE(Under) - GE(Over)))`\
197197`19     LinearDifference   Mix := Abs(Under - Over)`\
198198`20     Exclusion          Mix := GC(GE(Under) + GE(Over) - (GE(Under)*GE(Over) `**`shr`**` 15)`\
199199`21     LinearExclusion    Mix := Under + Over - (Under*Over `**`shr`**` 7)`\
200200`22     Subtract           Mix := GC(Max(0, GE(Under) - GE(Over)))`\
201201`23     LinearSubtract     Mix := Max(0, Under - Over)`\
202202`24     SubtractInverse    Mix := GC(Max(0, GE(Under) - (65535 - GE(Over))))`\
203203`25     LinearSubtractInv  Mix := Max(0, Under - (255 - Over))`\
204-\
204+
205205`26     Negation           Mix := `**`if`**` GE(Under) + GE(Over) <= 65535 `**`then`**` GC(GE(Under) + GE(Over))`\
206206`                                 `**`else`**` GC(131071 - (GE(Under) + GE(Over)))`\
207207`27     LinearNegation     Mix := `**`if`**` Under + Over <= 255 `**`then`**` Under + Over`\
208208`                                 `**`else`**` 511 - (Under + Over)`\
209209`28     Xor                Mix := Under `**`xor`**` Over`\
210-\
210+
211211`29     SvgSoftLight       Mix := `**`if`**` Over <= 128 `**`then`**` Under - (((256 - 2*Over)* Under `**`shr`**` 8)*(255 - Under) `**`shr`**` 8)`\
212212`                                 `**`else`**` `**`if`**` Under <= 64 `**`then`**` Under + ((2*Over - 256) * (Under*7 - (4*Under*(4*Under + 256)*(256 - Under) `**`shr`**` 16)) `**`shr`**` 8)`\
213213`                                 `**`else`**` Under + ((2*Over - 255) * (ByteSqrt(Under)-Under) `**`shr`**` 8)`\
214214`30     Mask               ApplyOpacity := ((2*Over.Red + 4*Over.Green + Over.Blue)*Over.Alpha + 255*7*(255-Over.Alpha)) / (255*7)`\
215215`31     LinearMultiplySat  L := (min(Under.Red, Under.Green, Under.Blue) + max(Under.Red, Under.Green, Under.Blue)) / 2`\
216216`                          Mix := `**`if`**` Over = 0 `**`then`**` L`\
217217`                                 `**`else`**` (Under - L)*Over/255 + L`\
218218`32     LinearHue          Mix := HSL(Over.H, Under.S, Under.L)`\
219219`33     LinearColor        Mix := HSL(Over.H, Over.S, Under.L)`\
220220`34     LinearLightness    Mix := HSL(Under.H, Under.S, Over.L)`\
221221`35     LinearSaturation   Mix := HSL(Under.H, `**`if`**` Under.S <> 0 `**`then`**` Over.S `**`else`**` 0, Under.L)`
222222
223223`36     CorrectedHue       Mix := GSB(Over.H, Under.S, Under.L)`\
224224`37     CorrectedColor     Mix := GSB(Over.H, Over.S, Under.L)`\
225225`38     CorrectedLightness Mix := GSB(Under.H, Under.S, Over.L)`\
226226`39     CorrectedSat       Mix := GSB(Under.H, `**`if`**` Under.S <> 0 `**`then`**` Over.S `**`else`**` 0, Under.L)`
227227
228228For the mask, the color channels are unchanged and the ApplyOpacity value is used to multiply the destination alpha value:
229229
230230`Final.Alpha := Under.Alpha * ApplyOpacity / 255`
231231
232232For other blend modes, first the RGB channels are modified according to the underlying alpha value:
233233
234234`Mix := (Mix * Under.Alpha + Over * (255 - Under.Alpha)) / 255`\
235235`Mix.Alpha := Over.Alpha`
236236
237237In other words, if the layer underneath is transparent, the merged color is simply the color of the layer above.
238238
239239Then the Mix color is drawn over the underlying layer. If Mix.Alpha = 255 then the color is replace:
240240
241241` Final = Mix`
242242
243243If Mix.Alpha = 0 then the color is unchanged:
244244
245245` Final = Under`
246246
247247Otherwise, the color is blended using the regular drawing function (with or without gamma correction depending on the stack option).
248248
249249#### Computation of blend-over
250250
251251Linear blend-over:
252252
253253**`var`**` `\
254254`  a1f, a2f, a12, a12m: DWord; //unsigned`\
255255**`begin`**\
256256`  a12  := 65025 - (255 - UnderAlpha) * (255 - OverAlpha);`\
257257`  a1f := UnderAlpha * (255 - OverAlpha);`\
258258`  a2f := OverAlpha * 255;`\
259259`  Under := (Under * a1f + Over * a2f + (a12 `**`shr`**` 1)) `**`div`**` a12; //for each channel`\
260260`  UnderAlpha := (a12 + a12 `**`shr`**` 7) `**`shr`**` 8;   `\
261261**`end`**`;`
262262
263263Blend-over with gamma correction:
264264
265265**`var`**` `\
266266`  a1f, a2f, a12, a12m: DWord; //unsigned`\
267267**`begin`**\
268268`  a12  := 65025 - (255 - UnderAlpha) * (255 - OverAlpha);`\
269269`  a1f := UnderAlpha * (255 - OverAlpha);`\
270270`  a2f := OverAlpha * 255;`\
271271`  Under := GC( (GE(Under) * a1f + GE(Over) * a2f + (a12 `**`shr`**` 1)) `**`div`**` a12 ); //for each channel`\
272272`  UnderAlpha := (a12 + a12 `**`shr`**` 7) `**`shr`**` 8;   `\
273273**`end`**`;`
274274
275275### Directory structure
276276
277277The [[LazPaint LZP directory structure|directory structure]] is optional. It can contain additional information about the layered image, in particular originals used to render the layers. A reader that does not handle vectorial information can discard it. Its offset is determined by the layer stack header.
278278
279279---
280280*From the Free Pascal wiki, page "LazPaint LZP Format", as it stood on 2021-03-01 after a change by Circular, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
281281Category: LazPaint

Back to history