|
| 1 | +unit fpreadwebp; |
| 2 | + |
| 3 | +// libwebp (libwebp.dll wrapper. Implementation of reader, writter and TWEBPImage class) |
| 4 | +// copyright @LacaK |
| 5 | +// https://lacak.users.sourceforge.net/freepascal.html#libwebp |
| 6 | + |
| 7 | +// reads WEBP images. Requires "libwebp" library. |
| 8 | +// writes WEBP images. Requires "libwebp" library. |
| 9 | + |
| 10 | + |
| 11 | +{$mode ObjFPC}{$H+} |
| 12 | + |
| 13 | +{$DEFINE INCREMENTAL_DECODING} |
| 14 | + |
| 15 | +interface |
| 16 | + |
| 17 | +uses |
| 18 | + Classes, SysUtils, FPImage; |
| 19 | + |
| 20 | +type |
| 21 | + |
| 22 | + { TFPReaderWEBP } |
| 23 | + |
| 24 | + TFPReaderWEBP = class(TFPCustomImageReader) |
| 25 | + protected |
| 26 | + procedure InternalRead(Str: TStream; Img: TFPCustomImage); override; |
| 27 | + function InternalCheck(Str: TStream): boolean; override; |
| 28 | + end; |
| 29 | + |
| 30 | + { TFPWriterWEBP } |
| 31 | + |
| 32 | + TFPWriterWEBP = class(TFPCustomImageWriter) |
| 33 | + private |
| 34 | + FQuality: integer; |
| 35 | + protected |
| 36 | + procedure InternalWrite(Str: TStream; Img: TFPCustomImage); override; |
| 37 | + public |
| 38 | + constructor Create; override; |
| 39 | + property CompressionQuality: integer read FQuality write FQuality; |
| 40 | + end; |
| 41 | + |
| 42 | +implementation |
| 43 | + |
| 44 | +uses |
| 45 | + libwebp; |
| 46 | + |
| 47 | +{ TFPReaderWEBP } |
| 48 | + |
| 49 | +// ImageRead -> InternalCheck -> InternalRead |
| 50 | +procedure TFPReaderWEBP.InternalRead(Str: TStream; Img: TFPCustomImage); |
| 51 | + function BGRAToFPColor(B,G,R,A: Byte): TFPColor; inline; |
| 52 | + begin |
| 53 | + with Result do |
| 54 | + begin |
| 55 | + Red :=(R shl 8) or R; |
| 56 | + Green :=(G shl 8) or G; |
| 57 | + Blue :=(B shl 8) or B; |
| 58 | + Alpha :=(A shl 8) or A; |
| 59 | + end; |
| 60 | + end; |
| 61 | +var |
| 62 | + w,h,x,y: integer; |
| 63 | + ptr, p: Puint8_t; |
| 64 | + ContProgress: boolean; |
| 65 | +{$IFDEF INCREMENTAL_DECODING} |
| 66 | + output: WebPDecBuffer; |
| 67 | + decoder: PWebPIDecoder; |
| 68 | + status: VP8StatusCode; |
| 69 | + buffer: array[0..4095] of Byte; |
| 70 | + data_size: integer; |
| 71 | +{$ELSE} |
| 72 | + MStream: TMemoryStream; |
| 73 | +{$ENDIF} |
| 74 | +begin |
| 75 | + ContProgress:=True; |
| 76 | + Progress(psStarting, 0, False, Rect(0,0,0,0), '', ContProgress); |
| 77 | + if not ContProgress then Exit; |
| 78 | + |
| 79 | +{$IFDEF INCREMENTAL_DECODING} |
| 80 | + FillByte(buffer, SizeOf(buffer), 0); |
| 81 | + data_size := Str.Read(buffer, SizeOf(buffer)); |
| 82 | + if (data_size>0) and (WebPGetInfo(@buffer, SizeOf(buffer), @w, @h) = 0) then |
| 83 | + raise FPImageException.Create('Wrong WEBP image format!') |
| 84 | + else begin |
| 85 | + Img.SetSize(w, h); |
| 86 | + ptr := GetMem(4*w*h); |
| 87 | + |
| 88 | + WebPInitDecBuffer(@output); |
| 89 | + output.colorspace := MODE_BGRA; |
| 90 | + output.u.RGBA.rgba := ptr; // points to an external buffer |
| 91 | + output.u.RGBA.stride := 4*w; |
| 92 | + output.u.RGBA.size := 4*w*h; |
| 93 | + output.is_external_memory := 1; |
| 94 | + |
| 95 | + decoder := WebPINewDecoder(@output); |
| 96 | + if decoder <> nil then |
| 97 | + while data_size > 0 do begin |
| 98 | + status := WebPIAppend(decoder, @buffer[0], data_size); |
| 99 | + if (status <> VP8_STATUS_OK) and (status <> VP8_STATUS_SUSPENDED) then |
| 100 | + break; |
| 101 | + data_size := Str.Read(buffer, SizeOf(buffer)); |
| 102 | + end; |
| 103 | + |
| 104 | + WebPFreeDecBuffer(@output); |
| 105 | + WebPIDelete(decoder); // config.output memory is preserved. |
| 106 | + end; |
| 107 | +{$ELSE} |
| 108 | + MStream := TMemoryStream.Create; |
| 109 | + try |
| 110 | + MStream.LoadFromStream(Str); |
| 111 | + |
| 112 | + ptr := WebPDecodeBGRA(MStream.Memory, MStream.Size, @w, @h); |
| 113 | + if ptr = nil then |
| 114 | + raise FPImageException.Create('Wrong WEBP image format!') |
| 115 | + else begin |
| 116 | + Img.SetSize(w,h); |
| 117 | + |
| 118 | + p := ptr; |
| 119 | + for y:=0 to h-1 do begin |
| 120 | + for x:=0 to w-1 do |
| 121 | + // TFPCustomImage provides only setter method? (Does not implements memory bitmap where BGRA values can be written directly) |
| 122 | + Img.Colors[x,y] := BGRAToFPColor(p[x shl 2], p[(x shl 2)+1], p[(x shl 2)+2], p[(x shl 2)+3]); |
| 123 | + Inc(p, w shl 2); |
| 124 | + end; |
| 125 | + end; |
| 126 | + finally |
| 127 | + MStream.Free; |
| 128 | + end; |
| 129 | +{$ENDIF} |
| 130 | + |
| 131 | + // WEBP image is fully decoded into buffer pointed by "ptr" |
| 132 | + p := ptr; |
| 133 | + for y:=0 to h-1 do begin |
| 134 | + for x:=0 to w-1 do |
| 135 | + // TFPCustomImage provides only setter method? (Does not implements memory bitmap where BGRA values can be written directly) |
| 136 | + Img.Colors[x,y] := BGRAToFPColor(p[x shl 2], p[(x shl 2)+1], p[(x shl 2)+2], p[(x shl 2)+3]); |
| 137 | + Inc(p, w shl 2); |
| 138 | + end; |
| 139 | + |
| 140 | +{$IFDEF INCREMENTAL_DECODING} |
| 141 | + FreeMem(ptr); |
| 142 | +{$ELSE} |
| 143 | + WebPFree(ptr); |
| 144 | +{$ENDIF} |
| 145 | +(* |
| 146 | + if WebPGetInfo(FStream.Memory, FStream.Size, @w, @h) <> 0 then begin |
| 147 | + Img.SetSize(w,h); |
| 148 | + bitmap := TBitmap.Create; |
| 149 | + bitmap.SetSize(w,h); |
| 150 | + bitmap.PixelFormat:=pf32bit; // BGRA |
| 151 | + if WebPDecodeBGRAInto(FStream.Memory, FStream.Size, bitmap.RawImage.Data, bitmap.RawImage.DataSize, bitmap.RawImage.Description.BytesPerLine) <> nil then |
| 152 | + Img.Assign(bitmap); |
| 153 | + bitmap.Free; |
| 154 | + end; |
| 155 | +*) |
| 156 | + Progress(FPimage.psEnding, 100, False, Rect(0,0,w,h), '', ContProgress); |
| 157 | +end; |
| 158 | + |
| 159 | +function TFPReaderWEBP.InternalCheck(Str: TStream): boolean; |
| 160 | +var |
| 161 | + p: Int64; |
| 162 | + Buf: array[0..3] of AnsiChar; |
| 163 | +begin |
| 164 | + if Str=nil then Exit(False); |
| 165 | + p:=Str.Position; |
| 166 | + Str.Position:=0; |
| 167 | + Result := (Str.Read(Buf, 4)=4) and (Buf='RIFF'); |
| 168 | + Str.Position:=8; |
| 169 | + Result := Result and (Str.Read(Buf, 4)=4) and (Buf='WEBP'); |
| 170 | + Str.Position:=p; |
| 171 | +end; |
| 172 | + |
| 173 | + |
| 174 | +{ TFPWriterWEBP } |
| 175 | + |
| 176 | +constructor TFPWriterWEBP.Create; |
| 177 | +begin |
| 178 | + inherited; |
| 179 | + FQuality := 100; |
| 180 | +end; |
| 181 | + |
| 182 | +// TFPCustomImage.SaveToStream -> TFPCustomImageWriter.ImageWrite -> InternalWrite |
| 183 | +procedure TFPWriterWEBP.InternalWrite(Str: TStream; Img: TFPCustomImage); |
| 184 | + function FPColorToBGRA(c: TFPColor): LongWord; inline; |
| 185 | + begin |
| 186 | + Result := (c.Blue shr 8) or ((c.Green shr 8) shl 8) or ((c.Red shr 8) shl 16) or ((c.Alpha shr 8) shl 24); |
| 187 | + end; |
| 188 | +var |
| 189 | + bgra,p: PLongWord; |
| 190 | + x,y,data_size: integer; |
| 191 | + output: Puint8_t; |
| 192 | +begin |
| 193 | + // first construct BGRA buffer from Img.Colors (is there smarter approach?) |
| 194 | + // (TFPCustomImage provides only getter/setter for Colors property) |
| 195 | + bgra := GetMem(Img.Width*Img.Height*4); |
| 196 | + p := bgra; |
| 197 | + for y:=0 to Img.Height-1 do begin |
| 198 | + for x:=0 to Img.Width-1 do |
| 199 | + p[x] := FPColorToBGRA(Img.Colors[x,y]); |
| 200 | + Inc(p, Img.Width); |
| 201 | + end; |
| 202 | + // then encode WEBP image |
| 203 | + data_size := WebPEncodeBGRA(Puint8_t(bgra), Img.Width, Img.Height, Img.Width*4, FQuality, output); |
| 204 | + // and save to stream |
| 205 | + Str.Write(output^, data_size); |
| 206 | + WebPFree(output); |
| 207 | + FreeMem(bgra); |
| 208 | +end; |
| 209 | + |
| 210 | + |
| 211 | +initialization |
| 212 | + if LoadLibwebp() then begin |
| 213 | + ImageHandlers.RegisterImageReader('WEBP Graphics', 'webp', TFPReaderWEBP); |
| 214 | + ImageHandlers.RegisterImageWriter('WEBP Graphics', 'webp', TFPWriterWEBP); |
| 215 | + end; |
| 216 | + |
| 217 | +end. |
| 218 | + |
0 commit comments