source: trunk/Packages/bgrabitmap/bgrapaintnet.pas

Last change on this file was 2, checked in by chronos, 5 years ago
File size: 17.5 KB
Line 
1unit BGRAPaintNet;
2
3{$mode objfpc}{$H+}
4
5interface
6
7{ This unit reads Paint.NET files. It needs BGRADNetDeserial to deserialize binary .Net objects.
8
9 A Paint.NET image consists in three parts :
10 - Xml header
11 - Binary serialized information (contains layer information)
12 - Compressed data (pixel data)
13
14 The class TPaintDotNetFile do not read the Xml header. ComputeFlatImage builds the resulting image
15 by using blending operations to merge layers.
16
17 The unit registers a TFPCustomImageReader so that it can be read by any image reading function of FreePascal,
18 and also registers a reader for BGRALayers }
19
20uses
21 Classes, SysUtils, BGRADNetDeserial, FPImage, BGRABitmapTypes, BGRABitmap, BGRALayers;
22
23type
24
25 { TPaintDotNetFile }
26
27 TPaintDotNetFile = class(TBGRACustomLayeredBitmap)
28 public
29 procedure LoadFromFile(const filenameUTF8: string); override;
30 procedure LoadFromStream(stream: TStream); override;
31 procedure Clear; override;
32 function ToString: ansistring; override;
33 function GetLayerBitmapCopy(layer: integer): TBGRABitmap; override;
34 constructor Create; override;
35 protected
36 procedure InternalLoadFromStream(stream: TStream);
37 function GetWidth: integer; override;
38 function GetHeight: integer; override;
39 function GetNbLayers: integer; override;
40 function GetBlendOperation(Layer: integer): TBlendOperation; override;
41 function GetLayerVisible(layer: integer): boolean; override;
42 function GetLayerOpacity(layer: integer): byte; override;
43 function GetLayerName(layer: integer): string; override;
44 private
45 Content: TDotNetDeserialization;
46 Document: TSerializedClass;
47 Layers: TSerializedClass;
48 LayerData: array of TMemoryStream;
49 function InternalGetLayer(num: integer): TSerializedClass;
50 function InternalGetBlendOperation(layer: TSerializedClass): TBlendOperation;
51 function InternalGetLayerName(layer: TSerializedClass): string;
52 function InternalGetLayerVisible(layer: TSerializedClass): boolean;
53 function InternalGetLayerOpacity(layer: TSerializedClass): byte;
54 function LayerDataSize(numLayer: integer): int64;
55 procedure LoadLayer(dest: TMemoryStream; src: TStream; uncompressedSize: int64);
56 end;
57
58 { TFPReaderPaintDotNet }
59
60 TFPReaderPaintDotNet = class(TFPCustomImageReader)
61 private
62 FWidth,FHeight,FNbLayers: integer;
63 protected
64 function InternalCheck(Stream: TStream): boolean; override;
65 procedure InternalRead(Stream: TStream; Img: TFPCustomImage); override;
66 public
67 property Width: integer read FWidth;
68 property Height: integer read FHeight;
69 property NbLayers: integer read FNbLayers;
70 end;
71
72function IsPaintDotNetFile(filename: string): boolean;
73function IsPaintDotNetFileUTF8(filenameUTF8: string): boolean;
74function IsPaintDotNetStream(stream: TStream): boolean;
75function LoadPaintDotNetFile(filename: string): TBGRABitmap;
76function LoadPaintDotNetFileUTF8(filenameUTF8: string): TBGRABitmap;
77
78procedure RegisterPaintNetFormat;
79
80implementation
81
82uses zstream, Math, BGRAUTF8;
83
84{$hints off}
85function BEReadLongword(Stream: TStream): longword;
86begin
87 Stream.Read(Result, sizeof(Result));
88 Result := BEtoN(Result);
89end;
90
91{$hints on}
92
93{$hints off}
94function BEReadLongint(Stream: TStream): longint;
95begin
96 Stream.Read(Result, sizeof(Result));
97 Result := BEtoN(Result);
98end;
99
100function IsPaintDotNetFile(filename: string): boolean;
101var
102 stream: TFileStream;
103begin
104 Result := False;
105 if FileExists(filename) then
106 begin
107 stream := TFileStream.Create(filename, fmOpenRead);
108 Result := IsPaintDotNetStream(stream);
109 stream.Free;
110 end;
111end;
112
113function IsPaintDotNetFileUTF8(filenameUTF8: string): boolean;
114var
115 stream: TFileStreamUTF8;
116begin
117 Result := False;
118 if FileExistsUTF8(filenameUTF8) then
119 begin
120 stream := TFileStreamUTF8.Create(filenameUTF8, fmOpenRead);
121 Result := IsPaintDotNetStream(stream);
122 stream.Free;
123 end;
124end;
125
126function IsPaintDotNetStream(stream: TStream): boolean;
127var
128 header: packed array[0..3] of char;
129 SavePos: int64;
130begin
131 Result := False;
132 try
133 if stream.Position + 4 < Stream.Size then
134 begin
135 header := #0#0#0#0;
136 SavePos := stream.Position;
137 stream.Read(header, 4);
138 stream.Position := SavePos;
139 if (header[0] = 'P') and (header[1] = 'D') and (header[2] = 'N') and
140 (header[3] = '3') then
141 Result := True;
142 end;
143 except
144 on ex: Exception do ;
145 end;
146end;
147
148function LoadPaintDotNetFile(filename: string): TBGRABitmap;
149begin
150 result := LoadPaintDotNetFileUTF8(SysToUTF8(filename));
151end;
152
153function LoadPaintDotNetFileUTF8(filenameUTF8: string): TBGRABitmap;
154var
155 pdn: TPaintDotNetFile;
156begin
157 pdn := TPaintDotNetFile.Create;
158 Result := nil;
159 try
160 pdn.LoadFromFile(filenameUTF8);
161 Result := pdn.ComputeFlatImage;
162 pdn.Free;
163 except
164 on ex: Exception do
165 begin
166 FreeAndNil(Result);
167 pdn.Free;
168 raise Exception.Create('Error while loading Paint.NET file. ' + ex.Message);
169 end;
170 end;
171end;
172
173function LoadPaintDotNetStream(stream: TStream): TBGRABitmap;
174var
175 pdn: TPaintDotNetFile;
176begin
177 pdn := TPaintDotNetFile.Create;
178 Result := nil;
179 try
180 pdn.LoadFromStream(stream);
181 Result := pdn.ComputeFlatImage;
182 pdn.Free;
183 except
184 on ex: Exception do
185 begin
186 FreeAndNil(Result);
187 pdn.Free;
188 raise Exception.Create('Error while loading Paint.NET stream. ' + ex.Message);
189 end;
190 end;
191end;
192
193{$hints on}
194
195{ TFPReaderPaintDotNet }
196
197function TFPReaderPaintDotNet.InternalCheck(Stream: TStream): boolean;
198begin
199 result := IsPaintDotNetStream(stream);
200end;
201
202procedure TFPReaderPaintDotNet.InternalRead(Stream: TStream; Img: TFPCustomImage);
203var
204 pdn: TPaintDotNetFile;
205 flat: TBGRABitmap;
206 x,y: integer;
207begin
208 FWidth := 0;
209 FHeight:= 0;
210 FNbLayers:= 0;
211 pdn := TPaintDotNetFile.Create;
212 try
213 pdn.LoadFromStream(Stream);
214 flat := pdn.ComputeFlatImage;
215 try
216 FWidth:= pdn.Width;
217 FHeight:= pdn.Height;
218 FNbLayers:= pdn.NbLayers;
219
220 if Img is TBGRACustomBitmap then
221 TBGRACustomBitmap(Img).Assign(flat) else
222 begin
223 Img.SetSize(pdn.Width,pdn.Height);
224 for y := 0 to pdn.Height-1 do
225 for x := 0 to pdn.Width-1 do
226 Img.Colors[x,y] := BGRAToFPColor(flat.GetPixel(x,y));
227 end;
228 finally
229 flat.free;
230 end;
231 pdn.Free;
232 except
233 on ex: Exception do
234 begin
235 pdn.Free;
236 raise Exception.Create('Error while loading Paint.NET file. ' + ex.Message);
237 end;
238 end;
239end;
240
241{ TPaintDotNetFile }
242
243procedure TPaintDotNetFile.LoadFromFile(const filenameUTF8: string);
244var
245 stream: TFileStreamUTF8;
246begin
247 stream := TFileStreamUTF8.Create(filenameUTF8, fmOpenRead);
248 OnLayeredBitmapLoadStart(filenameUTF8);
249 try
250 InternalLoadFromStream(stream);
251 finally
252 OnLayeredBitmapLoaded;
253 stream.Free;
254 end;
255end;
256
257procedure TPaintDotNetFile.LoadFromStream(stream: TStream);
258begin
259 OnLayeredBitmapLoadFromStreamStart;
260 try
261 InternalLoadFromStream(stream);
262 finally
263 OnLayeredBitmapLoaded;
264 end;
265end;
266
267procedure TPaintDotNetFile.InternalLoadFromStream(stream: TStream);
268var
269 header: packed array[0..3] of char;
270 XmlHeaderSize: integer;
271 CompressionFormat: word;
272 i: integer;
273begin
274 Clear;
275 header := #0#0#0#0;
276 stream.Read(header, 4);
277 if (header[0] <> 'P') or (header[1] <> 'D') or (header[2] <> 'N') or
278 (header[3] <> '3') then
279 raise Exception.Create('Invalid header');
280 XmlHeaderSize := 0;
281 stream.Read(XmlHeaderSize, 3);
282 XmlheaderSize := LEtoN(XmlheaderSize);
283 if Stream.Position + XmlHeaderSize > stream.Size then
284 raise Exception.Create('Xml header size error');
285 Stream.Position:= Stream.Position + XmlHeaderSize;
286 {$hints off}
287 stream.ReadBuffer(CompressionFormat, sizeof(CompressionFormat));
288 {$hints on}
289 CompressionFormat := LEToN(CompressionFormat);
290 Content := TDotNetDeserialization.Create;
291 case Compressionformat of
292 $0100: Content.LoadFromStream(Stream);
293 $8b1f: raise Exception.Create('Serialized data decompression not handled');
294 else
295 raise Exception.Create('Unknown compression format (' +
296 IntToStr(Compressionformat) + ')');
297 end;
298 Document := Content.FindClass('Document');
299 if Document <> nil then
300 Layers := Content.GetObjectField(Document, 'layers') as TSerializedClass;
301 SetLength(LayerData, NbLayers);
302 for i := 0 to NbLayers - 1 do
303 begin
304 OnLayeredBitmapLoadProgress((i+1)*100 div NbLayers);
305 LayerData[i] := TMemoryStream.Create;
306 LoadLayer(LayerData[i], Stream, LayerDataSize(i));
307 end;
308end;
309
310function TPaintDotNetFile.ToString: ansistring;
311var
312 i, j, nbbytes: integer;
313 b: byte;
314begin
315 Result := 'Paint.Net document' + LineEnding + LineEnding;
316 Result += Content.ToString;
317 for i := 0 to NbLayers - 1 do
318 begin
319 Result += LineEnding + 'Layer ' + IntToStr(i) + ' : ' + LayerName[i] + LineEnding;
320 Result += '[ ';
321 LayerData[i].Position := 0;
322 if LayerData[i].Size > 256 then
323 nbbytes := 256
324 else
325 nbbytes := LayerData[i].Size;
326 for j := 0 to nbbytes - 1 do
327 begin
328 {$hints off}
329 LayerData[i].ReadBuffer(b, 1);
330 {$hints on}
331 Result += IntToHex(b, 2) + ' ';
332 end;
333 if LayerData[i].Size > nbbytes then
334 Result += '...';
335 Result += ']' + lineending;
336 end;
337end;
338
339constructor TPaintDotNetFile.Create;
340begin
341 inherited Create;
342 Content := nil;
343 Document := nil;
344 Layers := nil;
345 LinearBlend := True;
346 RegisterPaintNetFormat;
347end;
348
349procedure TPaintDotNetFile.Clear;
350var
351 i: integer;
352begin
353 FreeAndNil(content);
354 document := nil;
355 Layers := nil;
356 for i := 0 to high(LayerData) do
357 LayerData[i].Free;
358 setLength(LayerData, 0);
359end;
360
361function TPaintDotNetFile.GetWidth: integer;
362begin
363 if Document = nil then
364 Result := 0
365 else
366 Result := StrToInt(Content.GetSimpleField(Document, 'width'));
367end;
368
369function TPaintDotNetFile.GetHeight: integer;
370begin
371 if Document = nil then
372 Result := 0
373 else
374 Result := StrToInt(Content.GetSimpleField(Document, 'height'));
375end;
376
377function TPaintDotNetFile.GetNbLayers: integer;
378begin
379 if Layers = nil then
380 Result := 0
381 else
382 Result := StrToInt(Content.GetSimpleField(Layers, '_size'));
383end;
384
385function TPaintDotNetFile.GetBlendOperation(Layer: integer): TBlendOperation;
386begin
387 Result := InternalGetBlendOperation(InternalGetLayer(layer));
388end;
389
390function TPaintDotNetFile.GetLayerVisible(layer: integer): boolean;
391begin
392 Result := InternalGetLayerVisible(InternalGetLayer(layer));
393end;
394
395function TPaintDotNetFile.GetLayerOpacity(layer: integer): byte;
396begin
397 Result := InternalGetLayerOpacity(InternalGetLayer(layer));
398end;
399
400function TPaintDotNetFile.GetLayerName(layer: integer): string;
401begin
402 Result := InternalGetLayerName(InternalGetLayer(layer));
403end;
404
405function TPaintDotNetFile.GetLayerBitmapCopy(layer: integer): TBGRABitmap;
406begin
407 if (layer < 0) or (layer >= NbLayers) then
408 raise Exception.Create('Index out of bounds');
409
410 Result := TBGRABitmap.Create(Width, Height);
411 if int64(Result.NbPixels) * 4 <> LayerData[layer].Size then
412 begin
413 Result.Free;
414 raise Exception.Create('Inconsistent layer data size');
415 end
416 else
417 begin
418 layerData[layer].Position := 0;
419 layerData[layer].Read(Result.Data^, LayerData[layer].Size);
420 if TBGRAPixel_RGBAOrder then result.SwapRedBlue;
421 Result.InvalidateBitmap;
422
423 if Result.LineOrder = riloBottomToTop then
424 Result.VerticalFlip;
425 end;
426end;
427
428function TPaintDotNetFile.InternalGetLayerName(layer: TSerializedClass): string;
429var
430 prop: TCustomSerializedObject;
431begin
432 if layer = nil then
433 Result := ''
434 else
435 begin
436 prop := Content.GetObjectField(layer, 'Layer+properties');
437 if prop = nil then
438 Result := ''
439 else
440 begin
441 Result := Content.GetSimpleField(prop, 'name');
442 end;
443 end;
444end;
445
446function TPaintDotNetFile.LayerDataSize(numLayer: integer): int64;
447var
448 layer, surface, scan0: TCustomSerializedObject;
449begin
450 layer := InternalGetLayer(numLayer);
451 if layer = nil then
452 Result := 0
453 else
454 begin
455 surface := Content.GetObjectField(layer, 'surface');
456 if surface = nil then
457 Result := 0
458 else
459 begin
460 scan0 := Content.GetObjectField(surface, 'scan0');
461 Result := StrToInt64(Content.GetSimpleField(scan0, 'length64'));
462 end;
463 end;
464end;
465
466procedure TPaintDotNetFile.LoadLayer(dest: TMemoryStream; src: TStream;
467 uncompressedSize: int64);
468var
469 CompressionFlag: byte;
470 maxChunkSize, decompressedChunkSize, compressedChunkSize: longword;
471 chunks: array of TMemoryStream;
472 numChunk: integer;
473 chunkCount, i: integer;
474 decomp: Tdecompressionstream;
475 nextPos: int64;
476
477begin
478 {$hints off}
479 src.ReadBuffer(CompressionFlag, 1);
480 {$hints on}
481 if CompressionFlag = 1 then
482 dest.CopyFrom(src, uncompressedSize)
483 else
484 if CompressionFlag = 0 then
485 begin
486 maxChunkSize := BEReadLongword(src);
487 if maxChunkSize < 4 then
488 raise Exception.Create('Invalid max chunk size');
489 chunkCount := (uncompressedSize + maxChunkSize - 1) div maxChunkSize;
490 setlength(chunks, chunkCount);
491 for i := 0 to ChunkCount - 1 do
492 begin
493 numChunk := BEReadLongint(src);
494 if (numChunk < 0) or (numChunk >= chunkCount) then
495 raise Exception.Create('Chunk number out of bounds');
496 compressedChunkSize := BEReadLongword(src);
497 nextPos := src.Position + compressedChunkSize;
498 src.Position := src.Position + 10; //skip gzip header
499 decompressedChunkSize :=
500 min(maxChunkSize, uncompressedSize - int64(numChunk) * int64(maxChunkSize));
501 decomp := Tdecompressionstream.Create(src, True);
502 chunks[numChunk] := TMemoryStream.Create;
503 chunks[numChunk].CopyFrom(decomp, decompressedChunkSize);
504 FreeAndNil(decomp);
505 src.Position := nextPos;
506 end;
507 for i := 0 to ChunkCount - 1 do
508 begin
509 chunks[i].Position := 0;
510 dest.CopyFrom(chunks[i], chunks[i].size);
511 chunks[i].Free;
512 end;
513 setlength(chunks, 0);
514 end
515 else
516 raise Exception('Unknown compression flag (' + IntToStr(CompressionFlag) + ')');
517end;
518
519function TPaintDotNetFile.InternalGetLayer(num: integer): TSerializedClass;
520var
521 layerList: TCustomSerializedObject;
522begin
523 if Layers = nil then
524 raise Exception.Create('No layers available')
525 else
526 if (num < 0) or (num >= NbLayers) then
527 raise Exception.Create('Layer index out of bounds')
528 else
529 begin
530 layerList := Content.GetObjectField(Layers, '_items');
531 Result := Content.GetObject(layerList.FieldAsString[num]) as TSerializedClass;
532 end;
533end;
534
535function TPaintDotNetFile.InternalGetBlendOperation(layer: TSerializedClass): TBlendOperation;
536var
537 prop, blendOp: TCustomSerializedObject;
538 blendName: string;
539begin
540 if layer = nil then
541 Result := boTransparent
542 else
543 begin
544 prop := Content.GetObjectField(layer, 'properties');
545 if prop = nil then
546 Result := boTransparent
547 else
548 begin
549 blendOp := Content.GetObjectField(prop, 'blendOp');
550 if blendOp = nil then
551 Result := boTransparent
552 else
553 begin
554 blendName := blendOp.TypeAsString;
555 if (pos('+', blendName) <> 0) then
556 Delete(blendName, 1, pos('+', blendName));
557 if copy(blendName, length(blendName) - length('BlendOp') +
558 1, length('BlendOp')) = 'BlendOp' then
559 Delete(blendName, length(blendName) - length('BlendOp') +
560 1, length('BlendOp'));
561
562 if blendName = 'Normal' then
563 Result := boTransparent
564 else
565 if blendName = 'Multiply' then
566 Result := boLinearMultiply
567 else
568 if blendName = 'Additive' then
569 Result := boLinearAdd
570 else
571 if blendName = 'ColorBurn' then
572 Result := boColorBurn
573 else
574 if blendName = 'ColorDodge' then
575 Result := boColorDodge
576 else
577 if blendName = 'Reflect' then
578 Result := boReflect
579 else
580 if blendName = 'Glow' then
581 Result := boGlow
582 else
583 if blendName = 'Overlay' then
584 Result := boOverlay
585 else
586 if blendName = 'Difference' then
587 Result := boLinearDifference
588 else
589 if blendName = 'Negation' then
590 Result := boLinearNegation
591 else
592 if blendName = 'Lighten' then
593 Result := boLighten
594 else
595 if blendName = 'Darken' then
596 Result := boDarken
597 else
598 if blendName = 'Screen' then
599 Result := boScreen
600 else
601 if blendName = 'Xor' then
602 Result := boXor
603 else
604 Result := boTransparent;
605 end;
606 end;
607 end;
608end;
609
610function TPaintDotNetFile.InternalGetLayerVisible(layer: TSerializedClass): boolean;
611var
612 prop: TCustomSerializedObject;
613begin
614 if layer = nil then
615 Result := False
616 else
617 begin
618 prop := Content.GetObjectField(layer, 'Layer+properties');
619 if prop = nil then
620 Result := False
621 else
622 begin
623 Result := (Content.GetSimpleField(prop, 'visible') = 'True');
624 end;
625 end;
626end;
627
628function TPaintDotNetFile.InternalGetLayerOpacity(layer: TSerializedClass): byte;
629var
630 prop: TCustomSerializedObject;
631begin
632 if layer = nil then
633 Result := 0
634 else
635 begin
636 prop := Content.GetObjectField(layer, 'Layer+properties');
637 if prop = nil then
638 Result := 0
639 else
640 begin
641 Result := StrToInt(Content.GetSimpleField(prop, 'opacity'));
642 end;
643 end;
644end;
645
646var AlreadyRegistered: boolean;
647
648procedure RegisterPaintNetFormat;
649begin
650 if AlreadyRegistered then exit;
651 ImageHandlers.RegisterImageReader ('Paint.NET image', 'pdn', TFPReaderPaintDotNet);
652 RegisterLayeredBitmapReader('pdn', TPaintDotNetFile);
653 //TPicture.RegisterFileFormat('pdn', 'Paint.NET image', TPaintDotNetFile);
654 DefaultBGRAImageReader[ifPaintDotNet] := TFPReaderPaintDotNet;
655 AlreadyRegistered := true;
656end;
657
658end.
Note: See TracBrowser for help on using the repository browser.