fpreadjpeg.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. { Copyright (C) 2003 Mattias Gaertner
  2. This library is free software; you can redistribute it and/or modify it
  3. under the terms of the GNU Library General Public License as published by
  4. the Free Software Foundation; either version 2 of the License, or (at your
  5. option) any later version.
  6. This program is distributed in the hope that it will be useful, but WITHOUT
  7. ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  8. FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  9. for more details.
  10. You should have received a copy of the GNU Library General Public License
  11. along with this library; if not, write to the Free Software Foundation,
  12. Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  13. ToDo:
  14. - palette
  15. }
  16. unit FPReadJPEG;
  17. {$mode objfpc}{$H+}
  18. interface
  19. uses
  20. Classes, SysUtils, FPImage, JPEGLib, JdAPImin, JDataSrc, JdAPIstd, JmoreCfg;
  21. type
  22. { TFPReaderJPEG }
  23. { This is a FPImage reader for jpeg images. }
  24. TFPReaderJPEG = class;
  25. PFPJPEGProgressManager = ^TFPJPEGProgressManager;
  26. TFPJPEGProgressManager = record
  27. pub : jpeg_progress_mgr;
  28. instance: TObject;
  29. last_pass: Integer;
  30. last_pct: Integer;
  31. last_time: Integer;
  32. last_scanline: Integer;
  33. end;
  34. TJPEGScale = (jsFullSize, jsHalf, jsQuarter, jsEighth);
  35. TJPEGReadPerformance = (jpBestQuality, jpBestSpeed);
  36. TFPReaderJPEG = class(TFPCustomImageReader)
  37. private
  38. FSmoothing: boolean;
  39. FMinHeight:integer;
  40. FMinWidth:integer;
  41. FWidth: Integer;
  42. FHeight: Integer;
  43. FGrayscale: boolean;
  44. FProgressiveEncoding: boolean;
  45. FError: jpeg_error_mgr;
  46. FProgressMgr: TFPJPEGProgressManager;
  47. FInfo: jpeg_decompress_struct;
  48. FScale: TJPEGScale;
  49. FPerformance: TJPEGReadPerformance;
  50. procedure SetPerformance(const AValue: TJPEGReadPerformance);
  51. procedure SetSmoothing(const AValue: boolean);
  52. protected
  53. procedure InternalRead(Str: TStream; Img: TFPCustomImage); override;
  54. function InternalCheck(Str: TStream): boolean; override;
  55. function InternalSize(Str:TStream): TPoint; override;
  56. public
  57. constructor Create; override;
  58. destructor Destroy; override;
  59. property GrayScale: boolean read FGrayscale;
  60. property ProgressiveEncoding: boolean read FProgressiveEncoding;
  61. property Smoothing: boolean read FSmoothing write SetSmoothing;
  62. property Performance: TJPEGReadPerformance read FPerformance write SetPerformance;
  63. property Scale: TJPEGScale read FScale write FScale;
  64. property MinWidth:integer read FMinWidth write FMinWidth;
  65. property MinHeight:integer read FMinHeight write FMinHeight;
  66. end;
  67. implementation
  68. procedure ReadCompleteStreamToStream(SrcStream, DestStream: TStream;
  69. StartSize: integer);
  70. var
  71. NewLength: Integer;
  72. ReadLen: Integer;
  73. Buffer: string;
  74. begin
  75. if (SrcStream is TMemoryStream) or (SrcStream is TFileStream)
  76. or (SrcStream is TStringStream)
  77. then begin
  78. // read as one block
  79. DestStream.CopyFrom(SrcStream,SrcStream.Size-SrcStream.Position);
  80. end else begin
  81. // read exponential
  82. if StartSize<=0 then StartSize:=1024;
  83. SetLength(Buffer,StartSize);
  84. NewLength:=0;
  85. repeat
  86. ReadLen:=SrcStream.Read(Buffer[NewLength+1],length(Buffer)-NewLength);
  87. inc(NewLength,ReadLen);
  88. if NewLength<length(Buffer) then break;
  89. SetLength(Buffer,length(Buffer)*2);
  90. until false;
  91. if NewLength>0 then
  92. DestStream.Write(Buffer[1],NewLength);
  93. end;
  94. end;
  95. procedure JPEGError(CurInfo: j_common_ptr);
  96. begin
  97. if CurInfo=nil then exit;
  98. raise Exception.CreateFmt('JPEG error',[CurInfo^.err^.msg_code]);
  99. end;
  100. procedure EmitMessage(CurInfo: j_common_ptr; msg_level: Integer);
  101. begin
  102. if CurInfo=nil then exit;
  103. if msg_level=0 then ;
  104. end;
  105. procedure OutputMessage(CurInfo: j_common_ptr);
  106. begin
  107. if CurInfo=nil then exit;
  108. end;
  109. procedure FormatMessage(CurInfo: j_common_ptr; var buffer: string);
  110. begin
  111. if CurInfo=nil then exit;
  112. {$ifdef FPC_Debug_Image}
  113. writeln('FormatMessage ',buffer);
  114. {$endif}
  115. end;
  116. procedure ResetErrorMgr(CurInfo: j_common_ptr);
  117. begin
  118. if CurInfo=nil then exit;
  119. CurInfo^.err^.num_warnings := 0;
  120. CurInfo^.err^.msg_code := 0;
  121. end;
  122. var
  123. jpeg_std_error: jpeg_error_mgr;
  124. procedure ProgressCallback(CurInfo: j_common_ptr);
  125. begin
  126. if CurInfo=nil then exit;
  127. // ToDo
  128. end;
  129. { TFPReaderJPEG }
  130. procedure TFPReaderJPEG.SetSmoothing(const AValue: boolean);
  131. begin
  132. if FSmoothing=AValue then exit;
  133. FSmoothing:=AValue;
  134. end;
  135. procedure TFPReaderJPEG.SetPerformance(const AValue: TJPEGReadPerformance);
  136. begin
  137. if FPerformance=AValue then exit;
  138. FPerformance:=AValue;
  139. end;
  140. procedure TFPReaderJPEG.InternalRead(Str: TStream; Img: TFPCustomImage);
  141. var
  142. MemStream: TMemoryStream;
  143. procedure SetSource;
  144. begin
  145. MemStream.Position:=0;
  146. jpeg_stdio_src(@FInfo, @MemStream);
  147. end;
  148. procedure ReadHeader;
  149. begin
  150. jpeg_read_header(@FInfo, TRUE);
  151. FWidth := FInfo.image_width;
  152. FHeight := FInfo.image_height;
  153. FGrayscale := FInfo.jpeg_color_space = JCS_GRAYSCALE;
  154. FProgressiveEncoding := jpeg_has_multiple_scans(@FInfo);
  155. end;
  156. procedure InitReadingPixels;
  157. var d1,d2:integer;
  158. function DToScale(inp:integer):TJPEGScale;
  159. begin
  160. if inp>7 then Result:=jsEighth else
  161. if inp>3 then Result:=jsQuarter else
  162. if inp>1 then Result:=jsHalf else
  163. Result:=jsFullSize;
  164. end;
  165. begin
  166. FInfo.scale_num := 1;
  167. if (FMinWidth>0) and (FMinHeight>0) then
  168. if (FInfo.image_width>FMinWidth) or (FInfo.image_height>FMinHeight) then
  169. begin
  170. d1:=Round((FInfo.image_width / FMinWidth)-0.5);
  171. d2:=Round((FInfo.image_height / FMinHeight)-0.5);
  172. if d1>d2 then fScale:=DToScale(d2) else fScale:=DtoScale(d1);
  173. end;
  174. FInfo.scale_denom :=1 shl Byte(FScale); //1
  175. FInfo.do_block_smoothing := FSmoothing;
  176. if FGrayscale then FInfo.out_color_space := JCS_GRAYSCALE;
  177. if (FInfo.out_color_space = JCS_GRAYSCALE) then
  178. begin
  179. FInfo.quantize_colors := True;
  180. FInfo.desired_number_of_colors := 236;
  181. end;
  182. if FPerformance = jpBestSpeed then
  183. begin
  184. FInfo.dct_method := JDCT_IFAST;
  185. FInfo.two_pass_quantize := False;
  186. FInfo.dither_mode := JDITHER_ORDERED;
  187. // FInfo.do_fancy_upsampling := False; can create an AV inside jpeglib
  188. end;
  189. if FProgressiveEncoding then
  190. begin
  191. FInfo.enable_2pass_quant := FInfo.two_pass_quantize;
  192. FInfo.buffered_image := True;
  193. end;
  194. end;
  195. function CorrectCMYK(const C: TFPColor): TFPColor;
  196. var
  197. MinColor: word;
  198. begin
  199. // accuracy not 100%
  200. if C.red<C.green then MinColor:=C.red
  201. else MinColor:= C.green;
  202. if C.blue<MinColor then MinColor:= C.blue;
  203. if MinColor+ C.alpha>$FF then MinColor:=$FF-C.alpha;
  204. Result.red:=(C.red-MinColor) shl 8;
  205. Result.green:=(C.green-MinColor) shl 8;
  206. Result.blue:=(C.blue-MinColor) shl 8;
  207. Result.alpha:=alphaOpaque;
  208. end;
  209. function CorrectYCCK(const C: TFPColor): TFPColor;
  210. var
  211. MinColor: word;
  212. begin
  213. if C.red<C.green then MinColor:=C.red
  214. else MinColor:= C.green;
  215. if C.blue<MinColor then MinColor:= C.blue;
  216. if MinColor+ C.alpha>$FF then MinColor:=$FF-C.alpha;
  217. Result.red:=(C.red-MinColor) shl 8;
  218. Result.green:=(C.green-MinColor) shl 8;
  219. Result.blue:=(C.blue-MinColor) shl 8;
  220. Result.alpha:=alphaOpaque;
  221. end;
  222. procedure ReadPixels;
  223. var
  224. Continue: Boolean;
  225. SampArray: JSAMPARRAY;
  226. SampRow: JSAMPROW;
  227. Color: TFPColor;
  228. LinesRead: Cardinal;
  229. x: Integer;
  230. y: Integer;
  231. c: word;
  232. Status,Scan: integer;
  233. ReturnValue,RestartLoop: Boolean;
  234. procedure OutputScanLines();
  235. var
  236. x: integer;
  237. begin
  238. Color.Alpha:=alphaOpaque;
  239. y:=0;
  240. while (FInfo.output_scanline < FInfo.output_height) do begin
  241. // read one line per call
  242. LinesRead := jpeg_read_scanlines(@FInfo, SampArray, 1);
  243. if LinesRead<1 then begin
  244. ReturnValue:=false;
  245. break;
  246. end;
  247. if (FInfo.jpeg_color_space = JCS_CMYK) then
  248. for x:=0 to FInfo.output_width-1 do begin
  249. Color.Red:=SampRow^[x*4+0];
  250. Color.Green:=SampRow^[x*4+1];
  251. Color.Blue:=SampRow^[x*4+2];
  252. Color.alpha:=SampRow^[x*4+3];
  253. Img.Colors[x,y]:=CorrectCMYK(Color);
  254. end
  255. else
  256. if (FInfo.jpeg_color_space = JCS_YCCK) then
  257. for x:=0 to FInfo.output_width-1 do begin
  258. Color.Red:=SampRow^[x*4+0];
  259. Color.Green:=SampRow^[x*4+1];
  260. Color.Blue:=SampRow^[x*4+2];
  261. Color.alpha:=SampRow^[x*4+3];
  262. Img.Colors[x,y]:=CorrectYCCK(Color);
  263. end
  264. else
  265. if fgrayscale then begin
  266. for x:=0 to FInfo.output_width-1 do begin
  267. c:= SampRow^[x] shl 8;
  268. Color.Red:=c;
  269. Color.Green:=c;
  270. Color.Blue:=c;
  271. Img.Colors[x,y]:=Color;
  272. end;
  273. end
  274. else begin
  275. for x:=0 to FInfo.output_width-1 do begin
  276. Color.Red:=SampRow^[x*3+0] shl 8;
  277. Color.Green:=SampRow^[x*3+1] shl 8;
  278. Color.Blue:=SampRow^[x*3+2] shl 8;
  279. Img.Colors[x,y]:=Color;
  280. end;
  281. end;
  282. inc(y);
  283. end;
  284. end;
  285. begin
  286. InitReadingPixels;
  287. Continue:=true;
  288. Progress(psStarting, 0, False, Rect(0,0,0,0), '', Continue);
  289. if not Continue then exit;
  290. jpeg_start_decompress(@FInfo);
  291. Img.SetSize(FInfo.output_width,FInfo.output_height);
  292. GetMem(SampArray,SizeOf(JSAMPROW));
  293. GetMem(SampRow,FInfo.output_width*FInfo.output_components);
  294. SampArray^[0]:=SampRow;
  295. try
  296. case FProgressiveEncoding of
  297. false:
  298. begin
  299. ReturnValue:=true;
  300. OutputScanLines();
  301. if FInfo.buffered_image then jpeg_finish_output(@FInfo);
  302. end;
  303. true:
  304. begin
  305. while true do begin
  306. (* The RestartLoop variable drops a placeholder for suspension
  307. mode, or partial jpeg decode, return and continue. In case
  308. of support this suspension, the RestartLoop:=True should be
  309. changed by an Exit and in the routine enter detects that it
  310. is being called from a suspended state to not
  311. reinitialize some buffer *)
  312. RestartLoop:=false;
  313. repeat
  314. status := jpeg_consume_input(@FInfo);
  315. until (status=JPEG_SUSPENDED) or (status=JPEG_REACHED_EOI);
  316. ReturnValue:=true;
  317. if FInfo.output_scanline = 0 then begin
  318. Scan := FInfo.input_scan_number;
  319. (* if we haven't displayed anything yet (output_scan_number==0)
  320. and we have enough data for a complete scan, force output
  321. of the last full scan *)
  322. if (FInfo.output_scan_number = 0) and (Scan > 1) and
  323. (status <> JPEG_REACHED_EOI) then Dec(Scan);
  324. if not jpeg_start_output(@FInfo, Scan) then begin
  325. RestartLoop:=true; (* I/O suspension *)
  326. end;
  327. end;
  328. if not RestartLoop then begin
  329. if (FInfo.output_scanline = $ffffff) then
  330. FInfo.output_scanline := 0;
  331. OutputScanLines();
  332. if ReturnValue=false then begin
  333. if (FInfo.output_scanline = 0) then begin
  334. (* didn't manage to read any lines - flag so we don't call
  335. jpeg_start_output() multiple times for the same scan *)
  336. FInfo.output_scanline := $ffffff;
  337. end;
  338. RestartLoop:=true; (* I/O suspension *)
  339. end;
  340. if not RestartLoop then begin
  341. if (FInfo.output_scanline = FInfo.output_height) then begin
  342. if not jpeg_finish_output(@FInfo) then begin
  343. RestartLoop:=true; (* I/O suspension *)
  344. end;
  345. if not RestartLoop then begin
  346. if (jpeg_input_complete(@FInfo) and
  347. (FInfo.input_scan_number = FInfo.output_scan_number)) then
  348. break;
  349. FInfo.output_scanline := 0;
  350. end;
  351. end;
  352. end;
  353. end;
  354. if RestartLoop then begin
  355. (* Suspension mode, but as not supported by this implementation
  356. it will simple break the loop to avoid endless looping. *)
  357. break;
  358. end;
  359. end;
  360. end;
  361. end;
  362. finally
  363. FreeMem(SampRow);
  364. FreeMem(SampArray);
  365. end;
  366. jpeg_finish_decompress(@FInfo);
  367. Progress(psEnding, 100, false, Rect(0,0,0,0), '', Continue);
  368. end;
  369. begin
  370. FWidth:=0;
  371. FHeight:=0;
  372. MemStream:=nil;
  373. FillChar(FInfo,SizeOf(FInfo),0);
  374. try
  375. if Str is TMemoryStream then
  376. MemStream:=TMemoryStream(Str)
  377. else begin
  378. MemStream:=TMemoryStream.Create;
  379. ReadCompleteStreamToStream(Str,MemStream,1024);
  380. MemStream.Position:=0;
  381. end;
  382. if MemStream.Size > 0 then begin
  383. FError:=jpeg_std_error;
  384. FInfo.err := @FError;
  385. jpeg_CreateDecompress(@FInfo, JPEG_LIB_VERSION, SizeOf(FInfo));
  386. try
  387. FProgressMgr.pub.progress_monitor := @ProgressCallback;
  388. FProgressMgr.instance := Self;
  389. FInfo.progress := @FProgressMgr.pub;
  390. SetSource;
  391. ReadHeader;
  392. ReadPixels;
  393. finally
  394. jpeg_Destroy_Decompress(@FInfo);
  395. end;
  396. end;
  397. finally
  398. if (MemStream<>nil) and (MemStream<>Str) then
  399. MemStream.Free;
  400. end;
  401. end;
  402. function TFPReaderJPEG.InternalSize(Str: TStream): TPoint;
  403. var
  404. JInfo: jpeg_decompress_struct;
  405. JError: jpeg_error_mgr;
  406. procedure SetSource;
  407. begin
  408. jpeg_stdio_src(@JInfo, @Str);
  409. end;
  410. procedure ReadHeader;
  411. begin
  412. jpeg_read_header(@JInfo, TRUE);
  413. Result.X := JInfo.image_width;
  414. Result.Y := JInfo.image_height;
  415. end;
  416. begin
  417. FillChar(JInfo,SizeOf(JInfo),0);
  418. if Str.Position < Str.Size then begin
  419. JError:=jpeg_std_error;
  420. JInfo.err := @JError;
  421. jpeg_CreateDecompress(@JInfo, JPEG_LIB_VERSION, SizeOf(JInfo));
  422. try
  423. SetSource;
  424. ReadHeader;
  425. finally
  426. jpeg_Destroy_Decompress(@JInfo);
  427. end;
  428. end;
  429. end;
  430. function TFPReaderJPEG.InternalCheck(Str: TStream): boolean;
  431. var
  432. Buf: array[0..1] of Byte = (0, 0);
  433. p: Int64;
  434. begin
  435. if Str=nil then exit(false);
  436. p:=Str.Position;
  437. Result := (Str.Read(Buf, 2)=2) and (Buf[0]=$FF) and (Buf[1]=$D8); // byte sequence FFD8 = start of image
  438. Str.Position:=p;
  439. end;
  440. constructor TFPReaderJPEG.Create;
  441. begin
  442. FScale:=jsFullSize;
  443. FPerformance:=jpBestSpeed;
  444. inherited Create;
  445. end;
  446. destructor TFPReaderJPEG.Destroy;
  447. begin
  448. inherited Destroy;
  449. end;
  450. initialization
  451. with jpeg_std_error do begin
  452. error_exit:=@JPEGError;
  453. emit_message:=@EmitMessage;
  454. output_message:=@OutputMessage;
  455. format_message:=@FormatMessage;
  456. reset_error_mgr:=@ResetErrorMgr;
  457. end;
  458. ImageHandlers.RegisterImageReader ('JPEG Graphics', 'jpg;jpeg', TFPReaderJPEG);
  459. end.