ImagingJpeg.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. {
  2. $Id: ImagingJpeg.pas,v 1.13 2006/09/21 19:44:35 galfar Exp $
  3. Vampyre Imaging Library
  4. by Marek Mauder ([email protected])
  5. http://imaginglib.sourceforge.net
  6. The contents of this file are used with permission, subject to the Mozilla
  7. Public License Version 1.1 (the "License"); you may not use this file except
  8. in compliance with the License. You may obtain a copy of the License at
  9. http://www.mozilla.org/MPL/MPL-1.1.html
  10. Software distributed under the License is distributed on an "AS IS" basis,
  11. WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
  12. the specific language governing rights and limitations under the License.
  13. Alternatively, the contents of this file may be used under the terms of the
  14. GNU Lesser General Public License (the "LGPL License"), in which case the
  15. provisions of the LGPL License are applicable instead of those above.
  16. If you wish to allow use of your version of this file only under the terms
  17. of the LGPL License and not to allow others to use your version of this file
  18. under the MPL, indicate your decision by deleting the provisions above and
  19. replace them with the notice and other provisions required by the LGPL
  20. License. If you do not delete the provisions above, a recipient may use
  21. your version of this file under either the MPL or the LGPL License.
  22. For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
  23. }
  24. { This unit contains image format loader/saver for Jpeg images.}
  25. unit ImagingJpeg;
  26. {$I ImagingOptions.inc}
  27. { You can choose which Pascal JpegLib implementation will be used.
  28. IMJPEGLIB is version bundled with Imaging which works with all supported
  29. compilers and platforms.
  30. PASJPEG is original JpegLib translation or version modified for FPC
  31. (and shipped with it). You can use PASJPEG if this version is already
  32. linked with another part of your program and you don't want to have
  33. two quite large almost the same libraries linked to your exe.
  34. This is the case with Lazarus applications for example.}
  35. {$DEFINE IMJPEGLIB}
  36. { $DEFINE PASJPEG}
  37. interface
  38. uses
  39. ImagingTypes, Imaging,
  40. {$IFDEF IMJPEGLIB}
  41. imjpeglib, imjmorecfg, imjcomapi, imjdapimin,
  42. imjdapistd, imjcapimin, imjcapistd, imjdmarker, imjcparam,
  43. {$ENDIF}
  44. {$IFDEF PASJPEG}
  45. jpeglib, jmorecfg, jcomapi, jdapimin,
  46. jdapistd, jcapimin, jcapistd, jdmarker, jcparam,
  47. {$ENDIF}
  48. ImagingUtility;
  49. {$IF Defined(FPC) and Defined(PASJPEG)}
  50. { When using FPC's pasjpeg in FPC the channel order is BGR instead of RGB}
  51. {$DEFINE RGBSWAPPED}
  52. {$IFEND}
  53. type
  54. { Class for loading/saving Jpeg images. Supports load/save of
  55. 8 bit grayscale and 24 bit RGB images.}
  56. TJpegFileFormat = class(TImageFileFormat)
  57. private
  58. FGrayScale: Boolean;
  59. protected
  60. { Controls Jpeg save compression quality. It is number in range 1..100.
  61. 1 means small/ugly file, 100 means large/nice file. Accessible trough
  62. ImagingJpegQuality option.}
  63. FQuality: LongInt;
  64. { If True Jpeg images are saved in progressive format. Accessible trough
  65. ImagingJpegProgressive option.}
  66. FProgressive: LongBool;
  67. protected
  68. procedure SetJpegIO(const JpegIO: TIOFunctions); virtual;
  69. function GetSupportedFormats: TImageFormats; override;
  70. procedure LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  71. OnlyFirstLevel: Boolean); override;
  72. procedure SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  73. Index: LongInt); override;
  74. function MakeCompatible(const Image: TImageData; var Comp: TImageData): Boolean; override;
  75. public
  76. constructor Create; override;
  77. function TestFormat(Handle: TImagingHandle): Boolean; override;
  78. end;
  79. const
  80. SJpegExtensions = 'jpg,jpeg,jfif,jpe,jif';
  81. SJpegFormatName = 'Joint Photographic Experts Group Image';
  82. JpegSupportedFormats: TImageFormats = [ifR8G8B8, ifGray8];
  83. JpegDefaultQuality = 90;
  84. JpegDefaultProgressive = False;
  85. implementation
  86. type
  87. TChar3 = array[0..2] of Char;
  88. const
  89. { Jpeg file identifier.}
  90. JpegMagic: TChar3 = #$FF#$D8#$FF;
  91. BufferSize = 16384;
  92. type
  93. TJpegContext = record
  94. case Byte of
  95. 0: (common: jpeg_common_struct);
  96. 1: (d: jpeg_decompress_struct);
  97. 2: (c: jpeg_compress_struct);
  98. end;
  99. TSourceMgr = record
  100. Pub: jpeg_source_mgr;
  101. Input: TImagingHandle;
  102. Buffer: JOCTETPTR;
  103. StartOfFile: Boolean;
  104. end;
  105. PSourceMgr = ^TSourceMgr;
  106. TDestMgr = record
  107. Pub: jpeg_destination_mgr;
  108. Output: TImagingHandle;
  109. Buffer: JOCTETPTR;
  110. end;
  111. PDestMgr = ^TDestMgr;
  112. var
  113. JIO: TIOFunctions;
  114. { Intenal unit jpeglib support functions }
  115. procedure JpegError(CurInfo: j_common_ptr);
  116. begin
  117. end;
  118. procedure EmitMessage(CurInfo: j_common_ptr; msg_level: Integer);
  119. begin
  120. end;
  121. procedure OutputMessage(CurInfo: j_common_ptr);
  122. begin
  123. end;
  124. procedure FormatMessage(CurInfo: j_common_ptr; var buffer: string);
  125. begin
  126. end;
  127. procedure ResetErrorMgr(CurInfo: j_common_ptr);
  128. begin
  129. CurInfo^.err^.num_warnings := 0;
  130. CurInfo^.err^.msg_code := 0;
  131. end;
  132. var
  133. JpegErrorRec: jpeg_error_mgr = (
  134. error_exit: JpegError;
  135. emit_message: EmitMessage;
  136. output_message: OutputMessage;
  137. format_message: FormatMessage;
  138. reset_error_mgr: ResetErrorMgr);
  139. procedure ReleaseContext(var jc: TJpegContext);
  140. begin
  141. if jc.common.err = nil then
  142. Exit;
  143. jpeg_destroy(@jc.common);
  144. jpeg_destroy_decompress(@jc.d);
  145. jpeg_destroy_compress(@jc.c);
  146. jc.common.err := nil;
  147. end;
  148. procedure InitSource(cinfo: j_decompress_ptr);
  149. begin
  150. PSourceMgr(cinfo.src).StartOfFile := True;
  151. end;
  152. function FillInputBuffer(cinfo: j_decompress_ptr): Boolean;
  153. var
  154. NBytes: LongInt;
  155. Src: PSourceMgr;
  156. begin
  157. Src := PSourceMgr(cinfo.src);
  158. NBytes := JIO.Read(Src.Input, Src.Buffer, BufferSize);
  159. if NBytes <= 0 then
  160. begin
  161. PChar(Src.Buffer)[0] := #$FF;
  162. PChar(Src.Buffer)[1] := Char(JPEG_EOI);
  163. NBytes := 2;
  164. end;
  165. Src.Pub.next_input_byte := Src.Buffer;
  166. Src.Pub.bytes_in_buffer := NBytes;
  167. Src.StartOfFile := False;
  168. Result := True;
  169. end;
  170. procedure SkipInputData(cinfo: j_decompress_ptr; num_bytes: LongInt);
  171. var
  172. Src: PSourceMgr;
  173. begin
  174. Src := PSourceMgr(cinfo.src);
  175. if num_bytes > 0 then
  176. begin
  177. while num_bytes > Src.Pub.bytes_in_buffer do
  178. begin
  179. Dec(num_bytes, Src.Pub.bytes_in_buffer);
  180. FillInputBuffer(cinfo);
  181. end;
  182. Src.Pub.next_input_byte := @PByteArray(Src.Pub.next_input_byte)[num_bytes];
  183. // Inc(LongInt(Src.Pub.next_input_byte), num_bytes);
  184. Dec(Src.Pub.bytes_in_buffer, num_bytes);
  185. end;
  186. end;
  187. procedure TermSource(cinfo: j_decompress_ptr);
  188. var
  189. Src: PSourceMgr;
  190. begin
  191. Src := PSourceMgr(cinfo.src);
  192. // move stream position back just after EOI marker so that more that one
  193. // JPEG images can be loaded from one stream
  194. JIO.Seek(Src.Input, -Src.Pub.bytes_in_buffer, smFromCurrent);
  195. end;
  196. procedure JpegStdioSrc(var cinfo: jpeg_decompress_struct; Handle:
  197. TImagingHandle);
  198. var
  199. Src: PSourceMgr;
  200. begin
  201. if cinfo.src = nil then
  202. begin
  203. cinfo.src := cinfo.mem.alloc_small(j_common_ptr(@cinfo), JPOOL_PERMANENT,
  204. SizeOf(TSourceMgr));
  205. Src := PSourceMgr(cinfo.src);
  206. Src.Buffer := cinfo.mem.alloc_small(j_common_ptr(@cinfo), JPOOL_PERMANENT,
  207. BufferSize * SizeOf(JOCTET));
  208. end;
  209. Src := PSourceMgr(cinfo.src);
  210. Src.Pub.init_source := InitSource;
  211. Src.Pub.fill_input_buffer := FillInputBuffer;
  212. Src.Pub.skip_input_data := SkipInputData;
  213. Src.Pub.resync_to_restart := jpeg_resync_to_restart;
  214. Src.Pub.term_source := TermSource;
  215. Src.Input := Handle;
  216. Src.Pub.bytes_in_buffer := 0;
  217. Src.Pub.next_input_byte := nil;
  218. end;
  219. procedure InitDest(cinfo: j_compress_ptr);
  220. var
  221. Dest: PDestMgr;
  222. begin
  223. Dest := PDestMgr(cinfo.dest);
  224. Dest.Pub.next_output_byte := Dest.Buffer;
  225. Dest.Pub.free_in_buffer := BufferSize;
  226. end;
  227. function EmptyOutput(cinfo: j_compress_ptr): Boolean;
  228. var
  229. Dest: PDestMgr;
  230. begin
  231. Dest := PDestMgr(cinfo.dest);
  232. JIO.Write(Dest.Output, Dest.Buffer, BufferSize);
  233. Dest.Pub.next_output_byte := Dest.Buffer;
  234. Dest.Pub.free_in_buffer := BufferSize;
  235. Result := True;
  236. end;
  237. procedure TermDest(cinfo: j_compress_ptr);
  238. var
  239. Dest: PDestMgr;
  240. DataCount: LongInt;
  241. begin
  242. Dest := PDestMgr(cinfo.dest);
  243. DataCount := BufferSize - Dest.Pub.free_in_buffer;
  244. if DataCount > 0 then
  245. JIO.Write(Dest.Output, Dest.Buffer, DataCount);
  246. end;
  247. procedure JpegStdioDest(var cinfo: jpeg_compress_struct; Handle:
  248. TImagingHandle);
  249. var
  250. Dest: PDestMgr;
  251. begin
  252. if cinfo.dest = nil then
  253. cinfo.dest := cinfo.mem.alloc_small(j_common_ptr(@cinfo),
  254. JPOOL_PERMANENT, SizeOf(TDestMgr));
  255. Dest := PDestMgr(cinfo.dest);
  256. Dest.Buffer := cinfo.mem.alloc_small(j_common_ptr(@cinfo), JPOOL_IMAGE,
  257. BufferSize * SIZEOF(JOCTET));
  258. Dest.Pub.init_destination := InitDest;
  259. Dest.Pub.empty_output_buffer := EmptyOutput;
  260. Dest.Pub.term_destination := TermDest;
  261. Dest.Output := Handle;
  262. end;
  263. procedure InitDecompressor(Handle: TImagingHandle; var jc: TJpegContext);
  264. begin
  265. FillChar(jc, sizeof(jc), 0);
  266. jc.common.err := @JpegErrorRec;
  267. jpeg_CreateDecompress(@jc.d, JPEG_LIB_VERSION, sizeof(jc.d));
  268. JpegStdioSrc(jc.d, Handle);
  269. jpeg_read_header(@jc.d, True);
  270. jc.d.scale_num := 1;
  271. jc.d.scale_denom := 1;
  272. jc.d.do_block_smoothing := True;
  273. if jc.d.out_color_space = JCS_GRAYSCALE then
  274. begin
  275. jc.d.quantize_colors := True;
  276. jc.d.desired_number_of_colors := 256;
  277. end;
  278. end;
  279. procedure InitCompressor(Handle: TImagingHandle; var jc: TJpegContext;
  280. Saver: TJpegFileFormat);
  281. begin
  282. FillChar(jc, sizeof(jc), 0);
  283. jc.common.err := @JpegErrorRec;
  284. jpeg_CreateCompress(@jc.c, JPEG_LIB_VERSION, sizeof(jc.c));
  285. JpegStdioDest(jc.c, Handle);
  286. jpeg_set_defaults(@jc.c);
  287. jpeg_set_quality(@jc.c, Saver.FQuality, True);
  288. if Saver.FGrayScale then
  289. jpeg_set_colorspace(@jc.c, JCS_GRAYSCALE)
  290. else
  291. jpeg_set_colorspace(@jc.c, JCS_YCbCr);
  292. if Saver.FProgressive then
  293. jpeg_simple_progression(@jc.c);
  294. end;
  295. { TJpegFileFormat class implementation }
  296. constructor TJpegFileFormat.Create;
  297. begin
  298. inherited Create;
  299. FName := SJpegFormatName;
  300. FCanLoad := True;
  301. FCanSave := True;
  302. FIsMultiImageFormat := False;
  303. FQuality := JpegDefaultQuality;
  304. FProgressive := JpegDefaultProgressive;
  305. AddExtensions(SJpegExtensions);
  306. RegisterOption(ImagingJpegQuality, @FQuality);
  307. RegisterOption(ImagingJpegProgressive, @FProgressive);
  308. end;
  309. function TJpegFileFormat.GetSupportedFormats: TImageFormats;
  310. begin
  311. Result := JpegSupportedFormats;
  312. end;
  313. procedure TJpegFileFormat.LoadData(Handle: TImagingHandle;
  314. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean);
  315. var
  316. PtrInc, LinesPerCall, LinesRead: LongInt;
  317. Dest: PByte;
  318. jc: TJpegContext;
  319. Format: TImageFormat;
  320. Info: TImageFormatInfo;
  321. {$IFDEF RGBSWAPPED}
  322. I: LongInt;
  323. Pix: PColor24Rec;
  324. {$ENDIF}
  325. begin
  326. // copy IO functions to global var used in JpegLib callbacks
  327. SetJpegIO(GetIO);
  328. SetLength(Images, 1);
  329. InitDecompressor(Handle, jc);
  330. if (jc.d.out_color_space = JCS_GRAYSCALE) then
  331. Format := ifGray8
  332. else
  333. Format := ifR8G8B8;
  334. NewImage(jc.d.image_width, jc.d.image_height, Format, Images[0]);
  335. with JIO, Images[0] do
  336. begin
  337. jpeg_start_decompress(@jc.d);
  338. GetImageFormatInfo(Format, Info);
  339. PtrInc := Width * Info.BytesPerPixel;
  340. LinesPerCall := 1;
  341. Dest := Bits;
  342. while (jc.d.output_scanline < jc.d.output_height) do
  343. begin
  344. LinesRead := jpeg_read_scanlines(@jc.d, @Dest, LinesPerCall);
  345. {$IFDEF RGBSWAPPED}
  346. if Format = ifR8G8B8 then
  347. begin
  348. Pix := PColor24Rec(Dest);
  349. for I := 0 to Width - 1 do
  350. begin
  351. SwapValues(Pix.R, Pix.B);
  352. Inc(Pix, 1);
  353. end;
  354. end;
  355. {$ENDIF}
  356. Inc(Dest, PtrInc * LinesRead);
  357. end;
  358. jpeg_finish_output(@jc.d);
  359. jpeg_finish_decompress(@jc.d);
  360. ReleaseContext(jc);
  361. end;
  362. end;
  363. procedure TJpegFileFormat.SaveData(Handle: TImagingHandle;
  364. const Images: TDynImageDataArray; Index: LongInt);
  365. var
  366. Len, PtrInc, LinesWritten: LongInt;
  367. Src, Line: PByte;
  368. jc: TJpegContext;
  369. ImageToSave: TImageData;
  370. Info: TImageFormatInfo;
  371. {$IFDEF RGBSWAPPED}
  372. I: LongInt;
  373. Pix: PColor24Rec;
  374. {$ENDIF}
  375. begin
  376. // check if option values are valid
  377. if not (FQuality in [1..100]) then
  378. FQuality := JpegDefaultQuality;
  379. // copy IO functions to global var used in JpegLib callbacks
  380. SetJpegIO(GetIO);
  381. Len := Length(Images);
  382. if Len = 0 then Exit;
  383. if (Index = MaxInt) or (Len = 1) then Index := 0;
  384. // makes image to save compatible with Jpeg saving capabilities
  385. if MakeCompatible(Images[Index], ImageToSave) then
  386. with JIO, ImageToSave do
  387. try
  388. GetImageFormatInfo(Format, Info);
  389. FGrayScale := Format = ifGray8;
  390. InitCompressor(Handle, jc, Self);
  391. jc.c.image_width := Width;
  392. jc.c.image_height := Height;
  393. if FGrayScale then
  394. begin
  395. jc.c.input_components := 1;
  396. jc.c.in_color_space := JCS_GRAYSCALE;
  397. end
  398. else
  399. begin
  400. jc.c.input_components := 3;
  401. jc.c.in_color_space := JCS_RGB;
  402. end;
  403. PtrInc := Width * Info.BytesPerPixel;
  404. Src := Bits;
  405. {$IFDEF RGBSWAPPED}
  406. GetMem(Line, PtrInc);
  407. {$ENDIF}
  408. jpeg_start_compress(@jc.c, True);
  409. while (jc.c.next_scanline < jc.c.image_height) do
  410. begin
  411. {$IFDEF RGBSWAPPED}
  412. if Format = ifR8G8B8 then
  413. begin
  414. Move(Src^, Line^, PtrInc);
  415. Pix := PColor24Rec(Line);
  416. for I := 0 to Width - 1 do
  417. begin
  418. SwapValues(Pix.R, Pix.B);
  419. Inc(Pix, 1);
  420. end;
  421. end;
  422. {$ELSE}
  423. Line := Src;
  424. {$ENDIF}
  425. LinesWritten := jpeg_write_scanlines(@jc.c, @Line, 1);
  426. Inc(Src, PtrInc * LinesWritten);
  427. end;
  428. jpeg_finish_compress(@jc.c);
  429. finally
  430. ReleaseContext(jc);
  431. if Images[Index].Bits <> ImageToSave.Bits then
  432. FreeImage(ImageToSave);
  433. {$IFDEF RGBSWAPPED}
  434. FreeMem(Line);
  435. {$ENDIF}
  436. end;
  437. end;
  438. function TJpegFileFormat.MakeCompatible(const Image: TImageData;
  439. var Comp: TImageData): Boolean;
  440. begin
  441. if not inherited MakeCompatible(Image, Comp) then
  442. begin
  443. if GetFormatInfo(Comp.Format).HasGrayChannel then
  444. ConvertImage(Comp, ifGray8)
  445. else
  446. ConvertImage(Comp, ifR8G8B8);
  447. end;
  448. Result := Comp.Format in GetSupportedFormats;
  449. end;
  450. function TJpegFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  451. var
  452. ReadCount: LongInt;
  453. ID: TChar3;
  454. begin
  455. Result := False;
  456. if Handle <> nil then
  457. with GetIO do
  458. begin
  459. FillChar(ID, SizeOf(ID), 0);
  460. ReadCount := Read(Handle, @ID, SizeOf(ID));
  461. Seek(Handle, -ReadCount, smFromCurrent);
  462. Result := (ReadCount = SizeOf(ID)) and (ID = JpegMagic);
  463. end;
  464. end;
  465. procedure TJpegFileFormat.SetJpegIO(const JpegIO: TIOFunctions);
  466. begin
  467. JIO := JpegIO;
  468. end;
  469. initialization
  470. RegisterImageFileFormat(TJpegFileFormat);
  471. {
  472. File Notes:
  473. -- TODOS ----------------------------------------------------
  474. - nothing now
  475. -- 0.19 Changes/Bug Fixes -----------------------------------
  476. - input position is now set correctly to the end of the image
  477. after loading is done. Loading of sequence of JPEG files stored in
  478. single stream works now
  479. - when loading and saving images in FPC with PASJPEG read and
  480. blue channels are swapped to have the same chanel order as IMJPEGLIB
  481. - you can now choose between IMJPEGLIB and PASJPEG implementations
  482. -- 0.17 Changes/Bug Fixes -----------------------------------
  483. - added SetJpegIO method which is used by JNG image format
  484. }
  485. end.