1 // SPDX-License-Identifier: LGPL-3.0-linking-exception
2 {
3     The original file is part of the Free Pascal run time library.
4     Copyright (c) 2003 by the Free Pascal development team
5 
6     PNG writer class modified by circular.
7 
8  **********************************************************************
9 
10  Fix for images with grayscale and alpha,
11  and for images with transparent pixels
12  }
13 unit BGRAWritePNG;
14 
15 {$mode objfpc}{$H+}
16 
17 interface
18 
19 
20 uses sysutils, BGRAClasses, FPImage, FPImgCmn, PNGcomn, ZStream, BGRABitmapTypes;
21 
22 type
23   THeaderChunk = packed record
24     Width, height : LongWord;
25     BitDepth, ColorType, Compression, Filter, Interlace : byte;
26   end;
27 
28   TGetPixelFunc = function (x,y : LongWord) : TColorData of object;
29   TGetPixelBGRAFunc = function (p: PBGRAPixel) : TColorData of object;
30 
31   TColorFormatFunction = function (color:TFPColor) : TColorData of object;
32 
33   { TBGRAWriterPNG }
34 
35   TBGRAWriterPNG = class (TBGRACustomWriterPNG)
36     private
37       FUsetRNS, FCompressedText, FWordSized, FIndexed,
38       FUseAlpha, FGrayScale : boolean;
39       FByteWidth : byte;
40       FChunk : TChunk;
41       CFmt : TColorFormat; // format of the colors to convert from
42       FFmtColor : TColorFormatFunction;
FTransparentColornull43       FTransparentColor : TFPColor;
44       FTransparentColorOk: boolean;
45       FSwitchLine, FCurrentLine, FPreviousLine : pByteArray;
46       FPalette : TFPPalette;
47       OwnsPalette : boolean;
48       FHeader : THeaderChunk;
49       FGetPixel : TGetPixelFunc;
50       FGetPixelBGRA : TGetPixelBGRAFunc;
51       FDatalineLength : LongWord;
52       ZData : TMemoryStream;  // holds uncompressed data until all blocks are written
53       Compressor : TCompressionStream; // compresses the data
54       FCompressionLevel : TCompressionLevel;
55       procedure WriteChunk;
GetColorPixelnull56       function GetColorPixel (x,y:LongWord) : TColorData;
GetPalettePixelnull57       function GetPalettePixel (x,y:LongWord) : TColorData;
GetColPalPixelnull58       function GetColPalPixel (x,y:LongWord) : TColorData;
GetColorPixelBGRAnull59       function GetColorPixelBGRA (p: PBGRAPixel) : TColorData;
GetPalettePixelBGRAnull60       function GetPalettePixelBGRA (p: PBGRAPixel) : TColorData;
GetColPalPixelBGRAnull61       function GetColPalPixelBGRA (p: PBGRAPixel) : TColorData;
62       procedure InitWriteIDAT;
63       procedure Gatherdata;
64       procedure WriteCompressedData;
65       procedure FinalWriteIDAT;
66     protected
67       property Header : THeaderChunk read FHeader;
68       procedure InternalWrite ({%H-}Str:TStream; {%H-}Img:TFPCustomImage); override;
GetUseAlphanull69       function GetUseAlpha: boolean; override;
70       procedure SetUseAlpha(AValue: boolean); override;
71       procedure WriteIHDR; virtual;
72       procedure WritePLTE; virtual;
73       procedure WritetRNS; virtual;
74       procedure WriteIDAT; virtual;
75       procedure WriteTexts; virtual;
76       procedure WriteIEND; virtual;
CurrentLinenull77       function CurrentLine (x:LongWord) : byte; inline;
PrevSamplenull78       function PrevSample (x:LongWord): byte; inline;
PreviousLinenull79       function PreviousLine (x:LongWord) : byte; inline;
PrevLinePrevSamplenull80       function PrevLinePrevSample (x:LongWord): byte; inline;
DoFilternull81       function  DoFilter (LineFilter:byte;index:LongWord; b:byte) : byte; virtual;
82       procedure SetChunkLength (aValue : LongWord);
83       procedure SetChunkType (ct : TChunkTypes); overload;
84       procedure SetChunkType (ct : TChunkCode); overload;
DecideGetPixelnull85       function DecideGetPixel : TGetPixelFunc; virtual;
DecideGetPixelBGRAnull86       function DecideGetPixelBGRA : TGetPixelBGRAFunc; virtual;
87       procedure DetermineHeader (var AHeader : THeaderChunk); virtual;
DetermineFilternull88       function DetermineFilter ({%H-}Current, {%H-}Previous:PByteArray; {%H-}linelength:LongWord):byte; virtual;
89       procedure FillScanLine (y : integer; ScanLine : pByteArray); virtual;
ColorDataGrayBnull90       function ColorDataGrayB(color:TFPColor) : TColorData;
ColorDataColorBnull91       function ColorDataColorB(color:TFPColor) : TColorData;
ColorDataGrayWnull92       function ColorDataGrayW(color:TFPColor) : TColorData;
ColorDataColorWnull93       function ColorDataColorW(color:TFPColor) : TColorData;
ColorDataGrayABnull94       function ColorDataGrayAB(color:TFPColor) : TColorData;
ColorDataColorABnull95       function ColorDataColorAB(color:TFPColor) : TColorData;
ColorDataGrayAWnull96       function ColorDataGrayAW(color:TFPColor) : TColorData;
ColorDataColorAWnull97       function ColorDataColorAW(color:TFPColor) : TColorData;
98       property ChunkDataBuffer : pByteArray read FChunk.data;
99       property UsetRNS : boolean read FUsetRNS;
100       property SingleTransparentColor : TFPColor read FTransparentColor;
101       property SingleTransparentColorOk : boolean read FTransparentColorOk;
102       property ThePalette : TFPPalette read FPalette;
103       property ColorFormat : TColorformat read CFmt;
readnull104       property ColorFormatFunc : TColorFormatFunction read FFmtColor;
105       property byteWidth : byte read FByteWidth;
106       property DatalineLength : LongWord read FDatalineLength;
107     public
108       constructor create; override;
109       destructor destroy; override;
110       property GrayScale : boolean read FGrayscale write FGrayScale;
111       property Indexed : boolean read FIndexed write FIndexed;
112       property CompressedText : boolean read FCompressedText write FCompressedText;
113       property WordSized : boolean read FWordSized write FWordSized;
114       property CompressionLevel : TCompressionLevel read FCompressionLevel write FCompressionLevel;
115   end;
116 
117 implementation
118 
119 constructor TBGRAWriterPNG.create;
120 begin
121   inherited;
122   Fchunk.acapacity := 0;
123   Fchunk.data := nil;
124   FGrayScale := False;
125   FIndexed := False;
126   FCompressedText := True;
127   FWordSized := False;
128   FUseAlpha := True;
129   FCompressionLevel:=clDefault;
130 end;
131 
132 destructor TBGRAWriterPNG.destroy;
133 begin
134   if OwnsPalette then FreeAndNil(FPalette);
135   with Fchunk do
136     if acapacity > 0 then
137       freemem (data);
138   inherited;
139 end;
140 
141 procedure TBGRAWriterPNG.WriteChunk;
142 var chead : TChunkHeader;
143     c : LongWord;
144 begin
145   with FChunk do
146     begin
147     {$IFDEF ENDIAN_LITTLE}
148     chead.CLength := swap (alength);
149     {$ELSE}
150     chead.CLength := alength;
151     {$ENDIF}
152 	if (ReadType = '') then
153       if atype <> ctUnknown then
154         chead.CType := ChunkTypes[aType]
155       else
156         raise PNGImageException.create ('Doesn''t have a chunktype to write')
157     else
158       chead.CType := ReadType;
159     c := CalculateCRC (All1Bits, ReadType, sizeOf(ReadType));
160     c := CalculateCRC (c, data^, alength);
161     {$IFDEF ENDIAN_LITTLE}
162     crc := swap(c xor All1Bits);
163     {$ELSE}
164     crc := c xor All1Bits;
165     {$ENDIF}
166     with TheStream do
167       begin
168       Write (chead, sizeof(chead));
169       Write (data^[0], alength);
170       Write (crc, sizeof(crc));
171       end;
172     end;
173 end;
174 
175 procedure TBGRAWriterPNG.SetChunkLength(aValue : LongWord);
176 begin
177   with Fchunk do
178     begin
179     alength := aValue;
180     if aValue > acapacity then
181       begin
182       if acapacity > 0 then
183         freemem (data);
184       GetMem (data, alength);
185       acapacity := alength;
186       end;
187     end;
188 end;
189 
190 procedure TBGRAWriterPNG.SetChunkType (ct : TChunkTypes);
191 begin
192   with Fchunk do
193     begin
194     aType := ct;
195     ReadType := ChunkTypes[ct];
196     end;
197 end;
198 
199 procedure TBGRAWriterPNG.SetChunkType (ct : TChunkCode);
200 begin
201   with FChunk do
202     begin
203     ReadType := ct;
204     aType := low(TChunkTypes);
205     while (aType < high(TChunkTypes)) and (ChunkTypes[aType] <> ct) do
206       inc (aType);
207     end;
208 end;
209 
CurrentLinenull210 function TBGRAWriterPNG.CurrentLine(x:LongWord):byte;
211 begin
212   result := FCurrentLine^[x];
213 end;
214 
PrevSamplenull215 function TBGRAWriterPNG.PrevSample (x:LongWord): byte;
216 begin
217   if x < byteWidth then
218     result := 0
219   else
220     result := FCurrentLine^[x - bytewidth];
221 end;
222 
PreviousLinenull223 function TBGRAWriterPNG.PreviousLine (x:LongWord) : byte;
224 begin
225   result := FPreviousline^[x];
226 end;
227 
TBGRAWriterPNG.PrevLinePrevSamplenull228 function TBGRAWriterPNG.PrevLinePrevSample (x:LongWord): byte;
229 begin
230   if x < byteWidth then
231     result := 0
232   else
233     result := FPreviousLine^[x - bytewidth];
234 end;
235 
DoFilternull236 function TBGRAWriterPNG.DoFilter(LineFilter:byte;index:LongWord; b:byte) : byte;
237 var diff : byte;
238   procedure FilterSub;
239   begin
240     diff := PrevSample(index);
241   end;
242   procedure FilterUp;
243   begin
244     diff := PreviousLine(index);
245   end;
246   procedure FilterAverage;
247   var l, p : word;
248   begin
249     l := PrevSample(index);
250     p := PreviousLine(index);
251     Diff := (l + p) div 2;
252   end;
253   procedure FilterPaeth;
254   var dl, dp, dlp : word; // index for previous and distances for:
255       l, p, lp : byte;  // r:predictor, Left, Previous, LeftPrevious
256       r : integer;
257   begin
258     l := PrevSample(index);
259     lp := PrevLinePrevSample(index);
260     p := PreviousLine(index);
261     r := Int32or64(l) + Int32or64(p) - Int32or64(lp);
262     dl := abs (r - l);
263     dlp := abs (r - lp);
264     dp := abs (r - p);
265     if (dl <= dp) and (dl <= dlp) then
266       diff := l
267     else if dp <= dlp then
268       diff := p
269     else
270       diff := lp;
271   end;
272 begin
273   case LineFilter of
274     0 : diff := 0;
275     1 : FilterSub;
276     2 : FilterUp;
277     3 : FilterAverage;
278     4 : FilterPaeth;
279   end;
280   if diff > b then
281     result := (b + $100 - diff)
282   else
283     result := b - diff;
284 end;
285 
286 procedure TBGRAWriterPNG.DetermineHeader (var AHeader : THeaderChunk);
287 var c : integer;
288 
ReducedColorEqualsnull289   function ReducedColorEquals(const c1,c2: TFPColor): boolean;
290   var g1,g2: word;
291   begin
292     if FGrayScale then
293       begin
294         g1 := CalculateGray(c1);
295         g2 := CalculateGray(c2);
296         if fwordsized then
297           result := (g1 = g2)
298         else
299           result := (g1 shr 8 = g2 shr 8);
300       end else
301       begin
302         if FWordSized then
303           result := (c1.red = c2.red) and (c1.green = c2.green) and (c1.blue = c2.blue)
304         else
305           result := (c1.red shr 8 = c2.red shr 8) and (c1.green shr 8 = c2.green shr 8) and (c1.blue shr 8 = c2.blue shr 8);
306       end;
307   end;
308 
CountAlphasnull309   function CountAlphas : integer;
310   var none, half : boolean;
311       maxTransparentAlpha: word;
312 
313     procedure CountFromPalettedImage;
314     var
315       p : integer;
316       a : word;
317       c : TFPColor;
318     begin
319       with TheImage.Palette do
320         begin
321         p := count-1;
322         FTransparentColor.alpha := alphaOpaque;
323         while (p >= 0) do
324           begin
325           c := color[p];
326           a := c.Alpha;
327           if a < FTransparentColor.alpha then //we're looking for the most transparent color
328             FTransparentColor := c;
329           if a <= maxTransparentAlpha then none := true
330           else if a <> alphaOpaque then half := true;
331           dec (p);
332           end;
333 
334         //check transparent color is used consistently
335         FTransparentColorOk := true;
336         p := count-1;
337         while (p >= 0) do
338           begin
339           c := color[p];
340           if c.alpha > maxTransparentAlpha then
341           begin
342             if ReducedColorEquals(c, FTransparentColor) then
343               begin
344               FTransparentColorOk := false;
345               break;
346               end;
347           end
348           else
349           begin
350             if not ReducedColorEquals(c, FTransparentColor) then
351               begin
352               FTransparentColorOk := false;
353               break;
354               end;
355           end;
356           dec(p);
357           end;
358         end;
359     end;
360 
361     procedure CountFromRGBImage;
362     var
363       a : word;
364       c : TFPColor;
365       x,y : longint;  // checks on < 0
366     begin
367       with TheImage do
368         begin
369         x := width-1;
370         y := height-1;
371         FTransparentColor.alpha := alphaOpaque;
372         while (y >= 0) and not half do //we stop if we already need a full alpha
373           begin
374           c := colors[x,y];
375           a := c.Alpha;
376           if a < FTransparentColor.alpha then //we're looking for the most transparent color
377             FTransparentColor := c;
378           if a <= maxTransparentAlpha then none := true
379           else if a <> alphaOpaque then half := true;
380           dec (x);
381           if (x < 0) then
382             begin
383             dec (y);
384             x := width-1;
385             end;
386           end;
387 
388         //check transparent color is used consistently
389         FTransparentColorOk := true;
390         x := width-1;
391         y := height-1;
392         while (y >= 0) do
393           begin
394           c := colors[x,y];
395           if c.alpha > maxTransparentAlpha then
396           begin
397             if ReducedColorEquals(c, FTransparentColor) then
398               begin
399               FTransparentColorOk := false;
400               break;
401               end;
402           end
403           else
404           begin
405             if not ReducedColorEquals(c, FTransparentColor) then
406               begin
407               FTransparentColorOk := false;
408               break;
409               end;
410           end;
411           dec (x);
412           if (x < 0) then
413             begin
414             dec (y);
415             x := width-1;
416             end;
417           end;
418         end;
419     end;
420 
421   begin
422     FTransparentColorOk := false;
423     if FWordSized then maxTransparentAlpha := 0
424     else maxTransparentAlpha := $00ff;
425     half := false;
426     none := false;
427     with TheImage do
428       if UsePalette then
429         CountFromPalettedImage
430       else
431         CountFromRGBImage;
432 
433     if half then
434       result := 3
435     else
436     if none then
437       begin
438       if FTransparentColorOk then
439         result := 2
440       else
441         result := 3;
442       end
443     else
444       result := 1;
445   end;
446   procedure DetermineColorFormat;
447   begin
448     with AHeader do
449       case colortype of
450         0 : if FWordSized then
451               begin
452               FFmtColor := @ColorDataGrayW;
453               FByteWidth := 2;
454               //CFmt := cfGray16
455               end
456             else
457               begin
458               FFmtColor := @ColorDataGrayB;
459               FByteWidth := 1;
460               //CFmt := cfGray8;
461               end;
462         2 : if FWordSized then
463               begin
464               FFmtColor := @ColorDataColorW;
465               FByteWidth := 6;
466               //CFmt := cfBGR48
467               end
468             else
469               begin
470               FFmtColor := @ColorDataColorB;
471               FByteWidth := 3;
472               //CFmt := cfBGR24;
473               end;
474         4 : if FWordSized then
475               begin
476               FFmtColor := @ColorDataGrayAW;
477               FByteWidth := 4;
478               //CFmt := cfGrayA32
479               end
480             else
481               begin
482               FFmtColor := @ColorDataGrayAB;
483               FByteWidth := 2;
484               //CFmt := cfGrayA16;
485               end;
486         6 : if FWordSized then
487               begin
488               FFmtColor := @ColorDataColorAW;
489               FByteWidth := 8;
490               //CFmt := cfABGR64
491               end
492             else
493               begin
494               FFmtColor := @ColorDataColorAB;
495               FByteWidth := 4;
496               //CFmt := cfABGR32;
497               end;
498       end;
499   end;
500 begin
501   with AHeader do
502     begin
503     {$IFDEF ENDIAN_LITTLE}
504     // problem: TheImage has integer width, PNG header LongWord width.
505     //          Integer Swap can give negative value
506     Width := swap (LongWord(TheImage.Width));
507     height := swap (LongWord(TheImage.Height));
508     {$ELSE}
509     Width := TheImage.Width;
510     height := TheImage.Height;
511     {$ENDIF}
512     if FUseAlpha then
513       c := CountAlphas
514     else
515       c := 0;
516     if FIndexed then
517       begin
518       if OwnsPalette then FreeAndNil(FPalette);
519       OwnsPalette := not TheImage.UsePalette;
520       if OwnsPalette then
521         begin
522         FPalette := TFPPalette.Create (16);
523         FPalette.Build (TheImage);
524         end
525       else
526         FPalette := TheImage.Palette;
527       if ThePalette.count > 256 then
528         raise PNGImageException.Create ('Too many colors to use indexed PNG color type');
529       ColorType := 3;
530       FUsetRNS := C > 1;
531       BitDepth := 8;
532       FByteWidth := 1;
533       end
534     else
535       begin
536       if c = 3 then
537         ColorType := 4;
538       FUsetRNS := (c = 2);
539       if not FGrayScale then
540         ColorType := ColorType + 2;
541       if FWordSized then
542         BitDepth := 16
543       else
544         BitDepth := 8;
545       DetermineColorFormat;
546       end;
547     Compression := 0;
548     Filter := 0;
549     Interlace := 0;
550     end;
551 end;
552 
553 procedure TBGRAWriterPNG.WriteIHDR;
554 begin
555   // signature for PNG
556   TheStream.writeBuffer(Signature,sizeof(Signature));
557   // Determine all settings for filling the header
558   fillchar(fheader,sizeof(fheader),#0);
559   DetermineHeader (FHeader);
560   // write the header chunk
561   SetChunkLength (sizeof(FHeader));
562   move (FHeader, ChunkDataBuffer^, sizeof(FHeader));
563   SetChunkType (ctIHDR);
564   WriteChunk;
565 end;
566 
567 { Color convertions }
568 
ColorDataGrayBnull569 function TBGRAWriterPNG.ColorDataGrayB(color:TFPColor) : TColorData;
570 var t : word;
571 begin
572   t := CalculateGray (color);
573   result := hi(t);
574 end;
575 
ColorDataGrayWnull576 function TBGRAWriterPNG.ColorDataGrayW(color:TFPColor) : TColorData;
577 begin
578   result := CalculateGray (color);
579 end;
580 
TBGRAWriterPNG.ColorDataGrayABnull581 function TBGRAWriterPNG.ColorDataGrayAB(color:TFPColor) : TColorData;
582 begin
583   result := ColorDataGrayB (color);
584   result := (color.Alpha and $ff00) or result;
585 end;
586 
TBGRAWriterPNG.ColorDataGrayAWnull587 function TBGRAWriterPNG.ColorDataGrayAW(color:TFPColor) : TColorData;
588 begin
589   result := ColorDataGrayW (color);
590   result := (color.Alpha shl 16) or result;
591 end;
592 
TBGRAWriterPNG.ColorDataColorBnull593 function TBGRAWriterPNG.ColorDataColorB(color:TFPColor) : TColorData;
594 begin
595   {$PUSH}{$HINTS OFF}
596   with color do
597     result := hi(red) + (green and $FF00) + (hi(blue) shl 16);
598   {$POP}
599 end;
600 
ColorDataColorWnull601 function TBGRAWriterPNG.ColorDataColorW(color:TFPColor) : TColorData;
602 begin
603   {$PUSH}{$HINTS OFF}
604   with color do
605     result := red + (green shl 16) + (qword(blue) shl 32);
606   {$POP}
607 end;
608 
ColorDataColorABnull609 function TBGRAWriterPNG.ColorDataColorAB(color:TFPColor) : TColorData;
610 begin
611   {$PUSH}{$HINTS OFF}
612   with color do
613     result := hi(red) + (green and $FF00) + (hi(blue) shl 16) + (hi(alpha) shl 24);
614   {$POP}
615 end;
616 
ColorDataColorAWnull617 function TBGRAWriterPNG.ColorDataColorAW(color:TFPColor) : TColorData;
618 begin
619   {$PUSH}{$HINTS OFF}
620   with color do
621     result := red + (green shl 16) + (qword(blue) shl 32) + (qword(alpha) shl 48);
622   {$POP}
623 end;
624 
625 { Data making routines }
626 
TBGRAWriterPNG.GetColorPixelnull627 function TBGRAWriterPNG.GetColorPixel (x,y:LongWord) : TColorData;
628 begin
629   result := FFmtColor (TheImage[x,y]);
630 end;
631 
TBGRAWriterPNG.GetPalettePixelnull632 function TBGRAWriterPNG.GetPalettePixel (x,y:LongWord) : TColorData;
633 begin
634   result := TheImage.Pixels[x,y];
635 end;
636 
TBGRAWriterPNG.GetColPalPixelnull637 function TBGRAWriterPNG.GetColPalPixel (x,y:LongWord) : TColorData;
638 begin
639   result := ThePalette.IndexOf (TheImage.Colors[x,y]);
640 end;
641 
TBGRAWriterPNG.GetColorPixelBGRAnull642 function TBGRAWriterPNG.GetColorPixelBGRA(p: PBGRAPixel): TColorData;
643 begin
644   result := FFmtColor(p^.ToFPColor);
645 end;
646 
GetPalettePixelBGRAnull647 function TBGRAWriterPNG.GetPalettePixelBGRA(p: PBGRAPixel): TColorData;
648 begin
649   result := TheImage.Palette.IndexOf(p^.ToFPColor);
650 end;
651 
GetColPalPixelBGRAnull652 function TBGRAWriterPNG.GetColPalPixelBGRA(p: PBGRAPixel): TColorData;
653 begin
654   result := ThePalette.IndexOf(p^.ToFPColor);
655 end;
656 
DecideGetPixelnull657 function TBGRAWriterPNG.DecideGetPixel : TGetPixelFunc;
658 begin
659   case Fheader.colortype of
660     3 : if TheImage.UsePalette then
661           result := @GetPalettePixel
662           else result := @GetColPalPixel;
663     else  result := @GetColorPixel;
664   end;
665 end;
666 
TBGRAWriterPNG.DecideGetPixelBGRAnull667 function TBGRAWriterPNG.DecideGetPixelBGRA: TGetPixelBGRAFunc;
668 begin
669   case Fheader.colortype of
670     3 : if TheImage.UsePalette then
671           result := @GetPalettePixelBGRA
672           else result := @GetColPalPixelBGRA;
673     else  result := @GetColorPixelBGRA;
674   end;
675 end;
676 
677 procedure TBGRAWriterPNG.WritePLTE;
678 var r,t : integer;
679     c : TFPColor;
680 begin
681   with ThePalette do
682     begin
683     SetChunkLength (count*3);
684     SetChunkType (ctPLTE);
685     t := 0;
686     For r := 0 to count-1 do
687       begin
688       c := Color[r];
689       ChunkdataBuffer^[t] := c.red div 256;
690       inc (t);
691       ChunkdataBuffer^[t] := c.green div 256;
692       inc (t);
693       ChunkdataBuffer^[t] := c.blue div 256;
694       inc (t);
695       end;
696     end;
697   WriteChunk;
698 end;
699 
700 procedure TBGRAWriterPNG.InitWriteIDAT;
701 begin
702   FDatalineLength := TheImage.Width*ByteWidth;
703   GetMem (FPreviousLine, FDatalineLength);
704   GetMem (FCurrentLine, FDatalineLength);
705   fillchar (FCurrentLine^,FDatalineLength,0);
706   ZData := TMemoryStream.Create;
707   Compressor := TCompressionStream.Create (FCompressionLevel,ZData);
708   FGetPixel := DecideGetPixel;
709   FGetPixelBGRA := DecideGetPixelBGRA;
710 end;
711 
712 procedure TBGRAWriterPNG.FinalWriteIDAT;
713 begin
714   ZData.Free;
715   FreeMem (FPreviousLine);
716   FreeMem (FCurrentLine);
717 end;
718 
TBGRAWriterPNG.DetermineFilternull719 function TBGRAWriterPNG.DetermineFilter (Current, Previous:PByteArray; linelength:LongWord) : byte;
720 begin
721   result := 0;
722 end;
723 
724 procedure TBGRAWriterPNG.FillScanLine (y : integer; ScanLine : pByteArray);
725 var x : integer;
726     cd : TColorData;
727     r, index : LongWord;
728     b : byte;
729     p : PBGRAPixel;
730 begin
731   index := 0;
732   if TheImage is TBGRACustomBitmap then
733   begin
734     p := TBGRACustomBitmap(TheImage).ScanLine[y];
735     if FHeader.BitDepth <> 16 then
736       case FByteWidth of
737         1: for x := pred(TheImage.Width) downto 0 do
738            begin
739              cd := FGetPixelBGRA(p);
740              ScanLine^[index] := cd;
741              inc (index);
742              inc(p);
743            end;
744         2: for x := pred(TheImage.Width) downto 0 do
745            begin
746              cd := FGetPixelBGRA(p);
747              ScanLine^[index] := cd and $ff;
748              ScanLine^[index+1] := cd shr 8;
749              inc (index,2);
750              inc(p);
751            end;
752         3: for x := pred(TheImage.Width) downto 0 do
753            begin
754              ScanLine^[index] := p^.red;
755              ScanLine^[index+1] := p^.green;
756              ScanLine^[index+2] := p^.blue;
757              inc (index,3);
758              inc(p);
759            end;
760         4: for x := pred(TheImage.Width) downto 0 do
761            begin
762              ScanLine^[index] := p^.red;
763              ScanLine^[index+1] := p^.green;
764              ScanLine^[index+2] := p^.blue;
765              ScanLine^[index+3] := p^.alpha;
766              inc (index,4);
767              inc(p);
768            end;
769         else raise exception.Create('Unexpected byte width');
770       end else
771       for x := pred(TheImage.Width) downto 0 do
772       begin
773         cd := FGetPixelBGRA(p);
774         {$IFDEF ENDIAN_BIG}
775         cd:=swap(cd);
776         {$ENDIF}
777         move (cd, ScanLine^[index], FBytewidth);
778         if WordSized then
779         begin
780           r := 0;
781           while (r+1 < FByteWidth) do
782             begin
783             b := Scanline^[index+r+1];
784             Scanline^[index+r+1] := Scanline^[index+r];
785             Scanline^[index+r] := b;
786             inc (r,2);
787             end;
788         end;
789         inc (index, FByteWidth);
790         inc(p);
791       end;
792   end
793   else
794   for x := 0 to pred(TheImage.Width) do
795     begin
796     cd := FGetPixel (x,y);
797     {$IFDEF ENDIAN_BIG}
798     cd:=swap(cd);
799     {$ENDIF}
800     move (cd, ScanLine^[index], FBytewidth);
801     if WordSized then
802       begin
803       r := 0;
804       while (r+1 < FByteWidth) do
805         begin
806         b := Scanline^[index+r+1];
807         Scanline^[index+r+1] := Scanline^[index+r];
808         Scanline^[index+r] := b;
809         inc (r,2);
810         end;
811       end;
812     inc (index, FByteWidth);
813     end;
814 end;
815 
816 procedure TBGRAWriterPNG.Gatherdata;
817 var x,y : integer;
818     lf : byte;
819 begin
820   for y := 0 to pred(TheImage.height) do
821     begin
822     FSwitchLine := FCurrentLine;
823     FCurrentLine := FPreviousLine;
824     FPreviousLine := FSwitchLine;
825     FillScanLine (y, FCurrentLine);
826     lf := DetermineFilter (FCurrentLine, FpreviousLine, FDataLineLength);
827     if lf <> 0 then
828       for x := 0 to FDatalineLength-1 do
829         FCurrentLine^[x] := DoFilter (lf, x, FCurrentLine^[x]);
830     Compressor.Write (lf, sizeof(lf));
831     Compressor.Write (FCurrentLine^, FDataLineLength);
832     end;
833 end;
834 
835 procedure TBGRAWriterPNG.WriteCompressedData;
836 var l : LongWord;
837 begin
838   Compressor.Free;  // Close compression and finish the writing in ZData
839   l := ZData.position;
840   ZData.position := 0;
841   SetChunkLength(l);
842   SetChunkType (ctIDAT);
843   ZData.Read (ChunkdataBuffer^, l);
844   WriteChunk;
845 end;
846 
847 procedure TBGRAWriterPNG.WriteIDAT;
848 begin
849   InitWriteIDAT;
850   GatherData;
851   WriteCompressedData;
852   FinalWriteIDAT;
853 end;
854 
855 procedure TBGRAWriterPNG.WritetRNS;
856   procedure PaletteAlpha;
857   var r : integer;
858   begin
859     with TheImage.palette do
860       begin
861       // search last palette entry with transparency
862       r := count;
863       repeat
864         dec (r);
865       until (r < 0) or (color[r].alpha <> alphaOpaque);
866       if r >= 0 then // there is at least 1 transparent color
867         begin
868         // from this color we go to the first palette entry
869         SetChunkLength (r+1);
870         repeat
871           chunkdatabuffer^[r] := (color[r].alpha shr 8);
872           dec (r);
873         until (r < 0);
874         end;
875       writechunk;
876       end;
877   end;
878   procedure GrayAlpha;
879   var g : word;
880   begin
881     SetChunkLength(2);
882     if WordSized then
883       g := CalculateGray (SingleTransparentColor)
884     else
885       g := hi (CalculateGray(SingleTransparentColor));
886     {$IFDEF ENDIAN_LITTLE}
887     g := swap (g);
888     {$ENDIF}
889     move (g,ChunkDataBuffer^[0],2);
890     WriteChunk;
891   end;
892   procedure ColorAlpha;
893   var g : TFPColor;
894   begin
895     SetChunkLength(6);
896     g := SingleTransparentColor;
897     with g do
898       if WordSized then
899         begin
900         {$IFDEF ENDIAN_LITTLE}
901         red := swap (red);
902         green := swap (green);
903         blue := swap (blue);
904         {$ENDIF}
905         move (g, ChunkDatabuffer^[0], 6);
906         end
907       else
908         begin
909         ChunkDataBuffer^[0] := 0;
910         ChunkDataBuffer^[1] := red shr 8;
911         ChunkDataBuffer^[2] := 0;
912         ChunkDataBuffer^[3] := green shr 8;
913         ChunkDataBuffer^[4] := 0;
914         ChunkDataBuffer^[5] := blue shr 8;
915         end;
916     WriteChunk;
917   end;
918 begin
919   SetChunkType (cttRNS);
920   case fheader.colortype of
921     6,4 : raise PNGImageException.create ('tRNS chunk forbidden for full alpha channels');
922     3 : PaletteAlpha;
923     2 : ColorAlpha;
924     0 : GrayAlpha;
925   end;
926 end;
927 
928 procedure TBGRAWriterPNG.WriteTexts;
929 begin
930 end;
931 
932 procedure TBGRAWriterPNG.WriteIEND;
933 begin
934   SetChunkLength(0);
935   SetChunkType (ctIEND);
936   WriteChunk;
937 end;
938 
939 procedure TBGRAWriterPNG.InternalWrite (Str:TStream; Img:TFPCustomImage);
940 begin
941   WriteIHDR;
942   if Fheader.colorType = 3 then
943     WritePLTE;
944   if FUsetRNS then
945     WritetRNS;
946   WriteIDAT;
947   WriteTexts;
948   WriteIEND;
949 end;
950 
TBGRAWriterPNG.GetUseAlphanull951 function TBGRAWriterPNG.GetUseAlpha: boolean;
952 begin
953   result := FUseAlpha;
954 end;
955 
956 procedure TBGRAWriterPNG.SetUseAlpha(AValue: boolean);
957 begin
958   FUseAlpha := AValue;
959 end;
960 
961 initialization
962 
963   DefaultBGRAImageWriter[ifPng] := TBGRAWriterPNG;
964 
965 end.
966