123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 |
- { Copyright (C) 2003 Mattias Gaertner
- This library is free software; you can redistribute it and/or modify it
- under the terms of the GNU Library General Public License as published by
- the Free Software Foundation; either version 2 of the License, or (at your
- option) any later version.
- This program is distributed in the hope that it will be useful, but WITHOUT
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
- for more details.
- You should have received a copy of the GNU Library General Public License
- along with this library; if not, write to the Free Software Foundation,
- Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- ToDo:
- - palette
- }
- unit FPReadJPEG;
- {$mode objfpc}{$H+}
- interface
- uses
- Classes, SysUtils, FPImage, JPEGLib, JdAPImin, JDataSrc, JdAPIstd, JmoreCfg;
- type
- { TFPReaderJPEG }
- { This is a FPImage reader for jpeg images. }
- TFPReaderJPEG = class;
- PFPJPEGProgressManager = ^TFPJPEGProgressManager;
- TFPJPEGProgressManager = record
- pub : jpeg_progress_mgr;
- instance: TObject;
- last_pass: Integer;
- last_pct: Integer;
- last_time: Integer;
- last_scanline: Integer;
- end;
- TJPEGScale = (jsFullSize, jsHalf, jsQuarter, jsEighth);
- TJPEGReadPerformance = (jpBestQuality, jpBestSpeed);
- TFPReaderJPEG = class(TFPCustomImageReader)
- private
- FSmoothing: boolean;
- FMinHeight:integer;
- FMinWidth:integer;
- FWidth: Integer;
- FHeight: Integer;
- FGrayscale: boolean;
- FProgressiveEncoding: boolean;
- FError: jpeg_error_mgr;
- FProgressMgr: TFPJPEGProgressManager;
- FInfo: jpeg_decompress_struct;
- FScale: TJPEGScale;
- FPerformance: TJPEGReadPerformance;
- procedure SetPerformance(const AValue: TJPEGReadPerformance);
- procedure SetSmoothing(const AValue: boolean);
- protected
- procedure InternalRead(Str: TStream; Img: TFPCustomImage); override;
- function InternalCheck(Str: TStream): boolean; override;
- function InternalSize(Str:TStream): TPoint; override;
- public
- constructor Create; override;
- destructor Destroy; override;
- property GrayScale: boolean read FGrayscale;
- property ProgressiveEncoding: boolean read FProgressiveEncoding;
- property Smoothing: boolean read FSmoothing write SetSmoothing;
- property Performance: TJPEGReadPerformance read FPerformance write SetPerformance;
- property Scale: TJPEGScale read FScale write FScale;
- property MinWidth:integer read FMinWidth write FMinWidth;
- property MinHeight:integer read FMinHeight write FMinHeight;
- end;
- implementation
- procedure ReadCompleteStreamToStream(SrcStream, DestStream: TStream;
- StartSize: integer);
- var
- NewLength: Integer;
- ReadLen: Integer;
- Buffer: string;
- begin
- if (SrcStream is TMemoryStream) or (SrcStream is TFileStream)
- or (SrcStream is TStringStream)
- then begin
- // read as one block
- DestStream.CopyFrom(SrcStream,SrcStream.Size-SrcStream.Position);
- end else begin
- // read exponential
- if StartSize<=0 then StartSize:=1024;
- SetLength(Buffer,StartSize);
- NewLength:=0;
- repeat
- ReadLen:=SrcStream.Read(Buffer[NewLength+1],length(Buffer)-NewLength);
- inc(NewLength,ReadLen);
- if NewLength<length(Buffer) then break;
- SetLength(Buffer,length(Buffer)*2);
- until false;
- if NewLength>0 then
- DestStream.Write(Buffer[1],NewLength);
- end;
- end;
- procedure JPEGError(CurInfo: j_common_ptr);
- begin
- if CurInfo=nil then exit;
- raise Exception.CreateFmt('JPEG error',[CurInfo^.err^.msg_code]);
- end;
- procedure EmitMessage(CurInfo: j_common_ptr; msg_level: Integer);
- begin
- if CurInfo=nil then exit;
- if msg_level=0 then ;
- end;
- procedure OutputMessage(CurInfo: j_common_ptr);
- begin
- if CurInfo=nil then exit;
- end;
- procedure FormatMessage(CurInfo: j_common_ptr; var buffer: string);
- begin
- if CurInfo=nil then exit;
- {$ifdef FPC_Debug_Image}
- writeln('FormatMessage ',buffer);
- {$endif}
- end;
- procedure ResetErrorMgr(CurInfo: j_common_ptr);
- begin
- if CurInfo=nil then exit;
- CurInfo^.err^.num_warnings := 0;
- CurInfo^.err^.msg_code := 0;
- end;
- var
- jpeg_std_error: jpeg_error_mgr;
- procedure ProgressCallback(CurInfo: j_common_ptr);
- begin
- if CurInfo=nil then exit;
- // ToDo
- end;
- { TFPReaderJPEG }
- procedure TFPReaderJPEG.SetSmoothing(const AValue: boolean);
- begin
- if FSmoothing=AValue then exit;
- FSmoothing:=AValue;
- end;
- procedure TFPReaderJPEG.SetPerformance(const AValue: TJPEGReadPerformance);
- begin
- if FPerformance=AValue then exit;
- FPerformance:=AValue;
- end;
- procedure TFPReaderJPEG.InternalRead(Str: TStream; Img: TFPCustomImage);
- var
- MemStream: TMemoryStream;
- procedure SetSource;
- begin
- MemStream.Position:=0;
- jpeg_stdio_src(@FInfo, @MemStream);
- end;
- procedure ReadHeader;
- begin
- jpeg_read_header(@FInfo, TRUE);
- FWidth := FInfo.image_width;
- FHeight := FInfo.image_height;
- FGrayscale := FInfo.jpeg_color_space = JCS_GRAYSCALE;
- FProgressiveEncoding := jpeg_has_multiple_scans(@FInfo);
- end;
- procedure InitReadingPixels;
- var d1,d2:integer;
- function DToScale(inp:integer):TJPEGScale;
- begin
- if inp>7 then Result:=jsEighth else
- if inp>3 then Result:=jsQuarter else
- if inp>1 then Result:=jsHalf else
- Result:=jsFullSize;
- end;
- begin
- FInfo.scale_num := 1;
- if (FMinWidth>0) and (FMinHeight>0) then
- if (FInfo.image_width>FMinWidth) or (FInfo.image_height>FMinHeight) then
- begin
- d1:=Round((FInfo.image_width / FMinWidth)-0.5);
- d2:=Round((FInfo.image_height / FMinHeight)-0.5);
- if d1>d2 then fScale:=DToScale(d2) else fScale:=DtoScale(d1);
- end;
- FInfo.scale_denom :=1 shl Byte(FScale); //1
- FInfo.do_block_smoothing := FSmoothing;
- if FGrayscale then FInfo.out_color_space := JCS_GRAYSCALE;
- if (FInfo.out_color_space = JCS_GRAYSCALE) then
- begin
- FInfo.quantize_colors := True;
- FInfo.desired_number_of_colors := 236;
- end;
- if FPerformance = jpBestSpeed then
- begin
- FInfo.dct_method := JDCT_IFAST;
- FInfo.two_pass_quantize := False;
- FInfo.dither_mode := JDITHER_ORDERED;
- // FInfo.do_fancy_upsampling := False; can create an AV inside jpeglib
- end;
- if FProgressiveEncoding then
- begin
- FInfo.enable_2pass_quant := FInfo.two_pass_quantize;
- FInfo.buffered_image := True;
- end;
- end;
- function CorrectCMYK(const C: TFPColor): TFPColor;
- var
- MinColor: word;
- begin
- // accuracy not 100%
- if C.red<C.green then MinColor:=C.red
- else MinColor:= C.green;
- if C.blue<MinColor then MinColor:= C.blue;
- if MinColor+ C.alpha>$FF then MinColor:=$FF-C.alpha;
- Result.red:=(C.red-MinColor) shl 8;
- Result.green:=(C.green-MinColor) shl 8;
- Result.blue:=(C.blue-MinColor) shl 8;
- Result.alpha:=alphaOpaque;
- end;
- function CorrectYCCK(const C: TFPColor): TFPColor;
- var
- MinColor: word;
- begin
- if C.red<C.green then MinColor:=C.red
- else MinColor:= C.green;
- if C.blue<MinColor then MinColor:= C.blue;
- if MinColor+ C.alpha>$FF then MinColor:=$FF-C.alpha;
- Result.red:=(C.red-MinColor) shl 8;
- Result.green:=(C.green-MinColor) shl 8;
- Result.blue:=(C.blue-MinColor) shl 8;
- Result.alpha:=alphaOpaque;
- end;
- procedure ReadPixels;
- var
- Continue: Boolean;
- SampArray: JSAMPARRAY;
- SampRow: JSAMPROW;
- Color: TFPColor;
- LinesRead: Cardinal;
- x: Integer;
- y: Integer;
- c: word;
- Status,Scan: integer;
- ReturnValue,RestartLoop: Boolean;
- procedure OutputScanLines();
- var
- x: integer;
- begin
- Color.Alpha:=alphaOpaque;
- y:=0;
- while (FInfo.output_scanline < FInfo.output_height) do begin
- // read one line per call
- LinesRead := jpeg_read_scanlines(@FInfo, SampArray, 1);
- if LinesRead<1 then begin
- ReturnValue:=false;
- break;
- end;
- if (FInfo.jpeg_color_space = JCS_CMYK) then
- for x:=0 to FInfo.output_width-1 do begin
- Color.Red:=SampRow^[x*4+0];
- Color.Green:=SampRow^[x*4+1];
- Color.Blue:=SampRow^[x*4+2];
- Color.alpha:=SampRow^[x*4+3];
- Img.Colors[x,y]:=CorrectCMYK(Color);
- end
- else
- if (FInfo.jpeg_color_space = JCS_YCCK) then
- for x:=0 to FInfo.output_width-1 do begin
- Color.Red:=SampRow^[x*4+0];
- Color.Green:=SampRow^[x*4+1];
- Color.Blue:=SampRow^[x*4+2];
- Color.alpha:=SampRow^[x*4+3];
- Img.Colors[x,y]:=CorrectYCCK(Color);
- end
- else
- if fgrayscale then begin
- for x:=0 to FInfo.output_width-1 do begin
- c:= SampRow^[x] shl 8;
- Color.Red:=c;
- Color.Green:=c;
- Color.Blue:=c;
- Img.Colors[x,y]:=Color;
- end;
- end
- else begin
- for x:=0 to FInfo.output_width-1 do begin
- Color.Red:=SampRow^[x*3+0] shl 8;
- Color.Green:=SampRow^[x*3+1] shl 8;
- Color.Blue:=SampRow^[x*3+2] shl 8;
- Img.Colors[x,y]:=Color;
- end;
- end;
- inc(y);
- end;
- end;
- begin
- InitReadingPixels;
- Continue:=true;
- Progress(psStarting, 0, False, Rect(0,0,0,0), '', Continue);
- if not Continue then exit;
- jpeg_start_decompress(@FInfo);
- Img.SetSize(FInfo.output_width,FInfo.output_height);
- GetMem(SampArray,SizeOf(JSAMPROW));
- GetMem(SampRow,FInfo.output_width*FInfo.output_components);
- SampArray^[0]:=SampRow;
- try
- case FProgressiveEncoding of
- false:
- begin
- ReturnValue:=true;
- OutputScanLines();
- if FInfo.buffered_image then jpeg_finish_output(@FInfo);
- end;
- true:
- begin
- while true do begin
- (* The RestartLoop variable drops a placeholder for suspension
- mode, or partial jpeg decode, return and continue. In case
- of support this suspension, the RestartLoop:=True should be
- changed by an Exit and in the routine enter detects that it
- is being called from a suspended state to not
- reinitialize some buffer *)
- RestartLoop:=false;
- repeat
- status := jpeg_consume_input(@FInfo);
- until (status=JPEG_SUSPENDED) or (status=JPEG_REACHED_EOI);
- ReturnValue:=true;
- if FInfo.output_scanline = 0 then begin
- Scan := FInfo.input_scan_number;
- (* if we haven't displayed anything yet (output_scan_number==0)
- and we have enough data for a complete scan, force output
- of the last full scan *)
- if (FInfo.output_scan_number = 0) and (Scan > 1) and
- (status <> JPEG_REACHED_EOI) then Dec(Scan);
- if not jpeg_start_output(@FInfo, Scan) then begin
- RestartLoop:=true; (* I/O suspension *)
- end;
- end;
- if not RestartLoop then begin
- if (FInfo.output_scanline = $ffffff) then
- FInfo.output_scanline := 0;
- OutputScanLines();
- if ReturnValue=false then begin
- if (FInfo.output_scanline = 0) then begin
- (* didn't manage to read any lines - flag so we don't call
- jpeg_start_output() multiple times for the same scan *)
- FInfo.output_scanline := $ffffff;
- end;
- RestartLoop:=true; (* I/O suspension *)
- end;
- if not RestartLoop then begin
- if (FInfo.output_scanline = FInfo.output_height) then begin
- if not jpeg_finish_output(@FInfo) then begin
- RestartLoop:=true; (* I/O suspension *)
- end;
- if not RestartLoop then begin
- if (jpeg_input_complete(@FInfo) and
- (FInfo.input_scan_number = FInfo.output_scan_number)) then
- break;
- FInfo.output_scanline := 0;
- end;
- end;
- end;
- end;
- if RestartLoop then begin
- (* Suspension mode, but as not supported by this implementation
- it will simple break the loop to avoid endless looping. *)
- break;
- end;
- end;
- end;
- end;
- finally
- FreeMem(SampRow);
- FreeMem(SampArray);
- end;
- jpeg_finish_decompress(@FInfo);
- Progress(psEnding, 100, false, Rect(0,0,0,0), '', Continue);
- end;
- begin
- FWidth:=0;
- FHeight:=0;
- MemStream:=nil;
- FillChar(FInfo,SizeOf(FInfo),0);
- try
- if Str is TMemoryStream then
- MemStream:=TMemoryStream(Str)
- else begin
- MemStream:=TMemoryStream.Create;
- ReadCompleteStreamToStream(Str,MemStream,1024);
- MemStream.Position:=0;
- end;
- if MemStream.Size > 0 then begin
- FError:=jpeg_std_error;
- FInfo.err := @FError;
- jpeg_CreateDecompress(@FInfo, JPEG_LIB_VERSION, SizeOf(FInfo));
- try
- FProgressMgr.pub.progress_monitor := @ProgressCallback;
- FProgressMgr.instance := Self;
- FInfo.progress := @FProgressMgr.pub;
- SetSource;
- ReadHeader;
- ReadPixels;
- finally
- jpeg_Destroy_Decompress(@FInfo);
- end;
- end;
- finally
- if (MemStream<>nil) and (MemStream<>Str) then
- MemStream.Free;
- end;
- end;
- function TFPReaderJPEG.InternalSize(Str: TStream): TPoint;
- var
- JInfo: jpeg_decompress_struct;
- JError: jpeg_error_mgr;
- procedure SetSource;
- begin
- jpeg_stdio_src(@JInfo, @Str);
- end;
- procedure ReadHeader;
- begin
- jpeg_read_header(@JInfo, TRUE);
- Result.X := JInfo.image_width;
- Result.Y := JInfo.image_height;
- end;
- begin
- FillChar(JInfo,SizeOf(JInfo),0);
- if Str.Position < Str.Size then begin
- JError:=jpeg_std_error;
- JInfo.err := @JError;
- jpeg_CreateDecompress(@JInfo, JPEG_LIB_VERSION, SizeOf(JInfo));
- try
- SetSource;
- ReadHeader;
- finally
- jpeg_Destroy_Decompress(@JInfo);
- end;
- end;
- end;
- function TFPReaderJPEG.InternalCheck(Str: TStream): boolean;
- var
- Buf: array[0..1] of Byte = (0, 0);
- p: Int64;
- begin
- if Str=nil then exit(false);
- p:=Str.Position;
- Result := (Str.Read(Buf, 2)=2) and (Buf[0]=$FF) and (Buf[1]=$D8); // byte sequence FFD8 = start of image
- Str.Position:=p;
- end;
- constructor TFPReaderJPEG.Create;
- begin
- FScale:=jsFullSize;
- FPerformance:=jpBestSpeed;
- inherited Create;
- end;
- destructor TFPReaderJPEG.Destroy;
- begin
- inherited Destroy;
- end;
- initialization
- with jpeg_std_error do begin
- error_exit:=@JPEGError;
- emit_message:=@EmitMessage;
- output_message:=@OutputMessage;
- format_message:=@FormatMessage;
- reset_error_mgr:=@ResetErrorMgr;
- end;
- ImageHandlers.RegisterImageReader ('JPEG Graphics', 'jpg;jpeg', TFPReaderJPEG);
- end.
|