ImagingJpeg.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. {
  2. $Id$
  3. Vampyre Imaging Library
  4. by Marek Mauder
  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. SysUtils, 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. FQuality: LongInt;
  61. FProgressive: LongBool;
  62. procedure SetJpegIO(const JpegIO: TIOFunctions); virtual;
  63. function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  64. OnlyFirstLevel: Boolean): Boolean; override;
  65. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  66. Index: LongInt): Boolean; override;
  67. procedure ConvertToSupported(var Image: TImageData;
  68. const Info: TImageFormatInfo); override;
  69. public
  70. constructor Create; override;
  71. function TestFormat(Handle: TImagingHandle): Boolean; override;
  72. procedure CheckOptionsValidity; override;
  73. published
  74. { Controls Jpeg save compression quality. It is number in range 1..100.
  75. 1 means small/ugly file, 100 means large/nice file. Accessible trough
  76. ImagingJpegQuality option.}
  77. property Quality: LongInt read FQuality write FQuality;
  78. { If True Jpeg images are saved in progressive format. Accessible trough
  79. ImagingJpegProgressive option.}
  80. property Progressive: LongBool read FProgressive write FProgressive;
  81. end;
  82. implementation
  83. const
  84. SJpegFormatName = 'Joint Photographic Experts Group Image';
  85. SJpegMasks = '*.jpg,*.jpeg,*.jfif,*.jpe,*.jif';
  86. JpegSupportedFormats: TImageFormats = [ifR8G8B8, ifGray8];
  87. JpegDefaultQuality = 90;
  88. JpegDefaultProgressive = False;
  89. const
  90. { Jpeg file identifiers.}
  91. JpegMagic: TChar2 = #$FF#$D8;
  92. JFIFSignature: TChar4 = 'JFIF';
  93. EXIFSignature: TChar4 = 'Exif';
  94. BufferSize = 16384;
  95. type
  96. TJpegContext = record
  97. case Byte of
  98. 0: (common: jpeg_common_struct);
  99. 1: (d: jpeg_decompress_struct);
  100. 2: (c: jpeg_compress_struct);
  101. end;
  102. TSourceMgr = record
  103. Pub: jpeg_source_mgr;
  104. Input: TImagingHandle;
  105. Buffer: JOCTETPTR;
  106. StartOfFile: Boolean;
  107. end;
  108. PSourceMgr = ^TSourceMgr;
  109. TDestMgr = record
  110. Pub: jpeg_destination_mgr;
  111. Output: TImagingHandle;
  112. Buffer: JOCTETPTR;
  113. end;
  114. PDestMgr = ^TDestMgr;
  115. var
  116. JIO: TIOFunctions;
  117. { Intenal unit jpeglib support functions }
  118. procedure JpegError(CurInfo: j_common_ptr);
  119. begin
  120. end;
  121. procedure EmitMessage(CurInfo: j_common_ptr; msg_level: Integer);
  122. begin
  123. end;
  124. procedure OutputMessage(CurInfo: j_common_ptr);
  125. begin
  126. end;
  127. procedure FormatMessage(CurInfo: j_common_ptr; var buffer: string);
  128. begin
  129. end;
  130. procedure ResetErrorMgr(CurInfo: j_common_ptr);
  131. begin
  132. CurInfo^.err^.num_warnings := 0;
  133. CurInfo^.err^.msg_code := 0;
  134. end;
  135. var
  136. JpegErrorRec: jpeg_error_mgr = (
  137. error_exit: JpegError;
  138. emit_message: EmitMessage;
  139. output_message: OutputMessage;
  140. format_message: FormatMessage;
  141. reset_error_mgr: ResetErrorMgr);
  142. procedure ReleaseContext(var jc: TJpegContext);
  143. begin
  144. if jc.common.err = nil then
  145. Exit;
  146. jpeg_destroy(@jc.common);
  147. jpeg_destroy_decompress(@jc.d);
  148. jpeg_destroy_compress(@jc.c);
  149. jc.common.err := nil;
  150. end;
  151. procedure InitSource(cinfo: j_decompress_ptr);
  152. begin
  153. PSourceMgr(cinfo.src).StartOfFile := True;
  154. end;
  155. function FillInputBuffer(cinfo: j_decompress_ptr): Boolean;
  156. var
  157. NBytes: LongInt;
  158. Src: PSourceMgr;
  159. begin
  160. Src := PSourceMgr(cinfo.src);
  161. NBytes := JIO.Read(Src.Input, Src.Buffer, BufferSize);
  162. if NBytes <= 0 then
  163. begin
  164. PChar(Src.Buffer)[0] := #$FF;
  165. PChar(Src.Buffer)[1] := Char(JPEG_EOI);
  166. NBytes := 2;
  167. end;
  168. Src.Pub.next_input_byte := Src.Buffer;
  169. Src.Pub.bytes_in_buffer := NBytes;
  170. Src.StartOfFile := False;
  171. Result := True;
  172. end;
  173. procedure SkipInputData(cinfo: j_decompress_ptr; num_bytes: LongInt);
  174. var
  175. Src: PSourceMgr;
  176. begin
  177. Src := PSourceMgr(cinfo.src);
  178. if num_bytes > 0 then
  179. begin
  180. while num_bytes > Src.Pub.bytes_in_buffer do
  181. begin
  182. Dec(num_bytes, Src.Pub.bytes_in_buffer);
  183. FillInputBuffer(cinfo);
  184. end;
  185. Src.Pub.next_input_byte := @PByteArray(Src.Pub.next_input_byte)[num_bytes];
  186. // Inc(LongInt(Src.Pub.next_input_byte), num_bytes);
  187. Dec(Src.Pub.bytes_in_buffer, num_bytes);
  188. end;
  189. end;
  190. procedure TermSource(cinfo: j_decompress_ptr);
  191. var
  192. Src: PSourceMgr;
  193. begin
  194. Src := PSourceMgr(cinfo.src);
  195. // Move stream position back just after EOI marker so that more that one
  196. // JPEG images can be loaded from one stream
  197. JIO.Seek(Src.Input, -Src.Pub.bytes_in_buffer, smFromCurrent);
  198. end;
  199. procedure JpegStdioSrc(var cinfo: jpeg_decompress_struct; Handle:
  200. TImagingHandle);
  201. var
  202. Src: PSourceMgr;
  203. begin
  204. if cinfo.src = nil then
  205. begin
  206. cinfo.src := cinfo.mem.alloc_small(j_common_ptr(@cinfo), JPOOL_PERMANENT,
  207. SizeOf(TSourceMgr));
  208. Src := PSourceMgr(cinfo.src);
  209. Src.Buffer := cinfo.mem.alloc_small(j_common_ptr(@cinfo), JPOOL_PERMANENT,
  210. BufferSize * SizeOf(JOCTET));
  211. end;
  212. Src := PSourceMgr(cinfo.src);
  213. Src.Pub.init_source := InitSource;
  214. Src.Pub.fill_input_buffer := FillInputBuffer;
  215. Src.Pub.skip_input_data := SkipInputData;
  216. Src.Pub.resync_to_restart := jpeg_resync_to_restart;
  217. Src.Pub.term_source := TermSource;
  218. Src.Input := Handle;
  219. Src.Pub.bytes_in_buffer := 0;
  220. Src.Pub.next_input_byte := nil;
  221. end;
  222. procedure InitDest(cinfo: j_compress_ptr);
  223. var
  224. Dest: PDestMgr;
  225. begin
  226. Dest := PDestMgr(cinfo.dest);
  227. Dest.Pub.next_output_byte := Dest.Buffer;
  228. Dest.Pub.free_in_buffer := BufferSize;
  229. end;
  230. function EmptyOutput(cinfo: j_compress_ptr): Boolean;
  231. var
  232. Dest: PDestMgr;
  233. begin
  234. Dest := PDestMgr(cinfo.dest);
  235. JIO.Write(Dest.Output, Dest.Buffer, BufferSize);
  236. Dest.Pub.next_output_byte := Dest.Buffer;
  237. Dest.Pub.free_in_buffer := BufferSize;
  238. Result := True;
  239. end;
  240. procedure TermDest(cinfo: j_compress_ptr);
  241. var
  242. Dest: PDestMgr;
  243. DataCount: LongInt;
  244. begin
  245. Dest := PDestMgr(cinfo.dest);
  246. DataCount := BufferSize - Dest.Pub.free_in_buffer;
  247. if DataCount > 0 then
  248. JIO.Write(Dest.Output, Dest.Buffer, DataCount);
  249. end;
  250. procedure JpegStdioDest(var cinfo: jpeg_compress_struct; Handle:
  251. TImagingHandle);
  252. var
  253. Dest: PDestMgr;
  254. begin
  255. if cinfo.dest = nil then
  256. cinfo.dest := cinfo.mem.alloc_small(j_common_ptr(@cinfo),
  257. JPOOL_PERMANENT, SizeOf(TDestMgr));
  258. Dest := PDestMgr(cinfo.dest);
  259. Dest.Buffer := cinfo.mem.alloc_small(j_common_ptr(@cinfo), JPOOL_IMAGE,
  260. BufferSize * SIZEOF(JOCTET));
  261. Dest.Pub.init_destination := InitDest;
  262. Dest.Pub.empty_output_buffer := EmptyOutput;
  263. Dest.Pub.term_destination := TermDest;
  264. Dest.Output := Handle;
  265. end;
  266. procedure InitDecompressor(Handle: TImagingHandle; var jc: TJpegContext);
  267. begin
  268. FillChar(jc, sizeof(jc), 0);
  269. jc.common.err := @JpegErrorRec;
  270. jpeg_CreateDecompress(@jc.d, JPEG_LIB_VERSION, sizeof(jc.d));
  271. JpegStdioSrc(jc.d, Handle);
  272. jpeg_read_header(@jc.d, True);
  273. jc.d.scale_num := 1;
  274. jc.d.scale_denom := 1;
  275. jc.d.do_block_smoothing := True;
  276. if jc.d.out_color_space = JCS_GRAYSCALE then
  277. begin
  278. jc.d.quantize_colors := True;
  279. jc.d.desired_number_of_colors := 256;
  280. end;
  281. end;
  282. procedure InitCompressor(Handle: TImagingHandle; var jc: TJpegContext;
  283. Saver: TJpegFileFormat);
  284. begin
  285. FillChar(jc, sizeof(jc), 0);
  286. jc.common.err := @JpegErrorRec;
  287. jpeg_CreateCompress(@jc.c, JPEG_LIB_VERSION, sizeof(jc.c));
  288. JpegStdioDest(jc.c, Handle);
  289. jpeg_set_defaults(@jc.c);
  290. jpeg_set_quality(@jc.c, Saver.FQuality, True);
  291. if Saver.FGrayScale then
  292. jpeg_set_colorspace(@jc.c, JCS_GRAYSCALE)
  293. else
  294. jpeg_set_colorspace(@jc.c, JCS_YCbCr);
  295. if Saver.FProgressive then
  296. jpeg_simple_progression(@jc.c);
  297. end;
  298. { TJpegFileFormat class implementation }
  299. constructor TJpegFileFormat.Create;
  300. begin
  301. inherited Create;
  302. FName := SJpegFormatName;
  303. FCanLoad := True;
  304. FCanSave := True;
  305. FIsMultiImageFormat := False;
  306. FSupportedFormats := JpegSupportedFormats;
  307. FQuality := JpegDefaultQuality;
  308. FProgressive := JpegDefaultProgressive;
  309. AddMasks(SJpegMasks);
  310. RegisterOption(ImagingJpegQuality, @FQuality);
  311. RegisterOption(ImagingJpegProgressive, @FProgressive);
  312. end;
  313. procedure TJpegFileFormat.CheckOptionsValidity;
  314. begin
  315. // Check if option values are valid
  316. if not (FQuality in [1..100]) then
  317. FQuality := JpegDefaultQuality;
  318. end;
  319. function TJpegFileFormat.LoadData(Handle: TImagingHandle;
  320. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  321. var
  322. PtrInc, LinesPerCall, LinesRead: LongInt;
  323. Dest: PByte;
  324. jc: TJpegContext;
  325. Info: TImageFormatInfo;
  326. {$IFDEF RGBSWAPPED}
  327. I: LongInt;
  328. Pix: PColor24Rec;
  329. {$ENDIF}
  330. begin
  331. // Copy IO functions to global var used in JpegLib callbacks
  332. SetJpegIO(GetIO);
  333. SetLength(Images, 1);
  334. with JIO, Images[0] do
  335. try
  336. InitDecompressor(Handle, jc);
  337. NewImage(jc.d.image_width, jc.d.image_height,
  338. IffFormat(jc.d.out_color_space = JCS_GRAYSCALE, ifGray8, ifR8G8B8), Images[0]);
  339. jpeg_start_decompress(@jc.d);
  340. GetImageFormatInfo(Format, Info);
  341. PtrInc := Width * Info.BytesPerPixel;
  342. LinesPerCall := 1;
  343. Dest := Bits;
  344. while jc.d.output_scanline < jc.d.output_height do
  345. begin
  346. LinesRead := jpeg_read_scanlines(@jc.d, @Dest, LinesPerCall);
  347. {$IFDEF RGBSWAPPED}
  348. if Format = ifR8G8B8 then
  349. begin
  350. Pix := PColor24Rec(Dest);
  351. for I := 0 to Width - 1 do
  352. begin
  353. SwapValues(Pix.R, Pix.B);
  354. Inc(Pix);
  355. end;
  356. end;
  357. {$ENDIF}
  358. Inc(Dest, PtrInc * LinesRead);
  359. end;
  360. jpeg_finish_output(@jc.d);
  361. jpeg_finish_decompress(@jc.d);
  362. Result := True;
  363. finally
  364. ReleaseContext(jc);
  365. end;
  366. end;
  367. function TJpegFileFormat.SaveData(Handle: TImagingHandle;
  368. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  369. var
  370. PtrInc, LinesWritten: LongInt;
  371. Src, Line: PByte;
  372. jc: TJpegContext;
  373. ImageToSave: TImageData;
  374. Info: TImageFormatInfo;
  375. MustBeFreed: Boolean;
  376. {$IFDEF RGBSWAPPED}
  377. I: LongInt;
  378. Pix: PColor24Rec;
  379. {$ENDIF}
  380. begin
  381. Result := False;
  382. // Copy IO functions to global var used in JpegLib callbacks
  383. SetJpegIO(GetIO);
  384. // Makes image to save compatible with Jpeg saving capabilities
  385. if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) 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. Result := True;
  430. finally
  431. ReleaseContext(jc);
  432. if MustBeFreed then
  433. FreeImage(ImageToSave);
  434. {$IFDEF RGBSWAPPED}
  435. FreeMem(Line);
  436. {$ENDIF}
  437. end;
  438. end;
  439. procedure TJpegFileFormat.ConvertToSupported(var Image: TImageData;
  440. const Info: TImageFormatInfo);
  441. begin
  442. if Info.HasGrayChannel then
  443. ConvertImage(Image, ifGray8)
  444. else
  445. ConvertImage(Image, ifR8G8B8);
  446. end;
  447. function TJpegFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  448. var
  449. ReadCount: LongInt;
  450. ID: array[0..9] of Char;
  451. begin
  452. Result := False;
  453. if Handle <> nil then
  454. with GetIO do
  455. begin
  456. FillChar(ID, SizeOf(ID), 0);
  457. ReadCount := Read(Handle, @ID, SizeOf(ID));
  458. Seek(Handle, -ReadCount, smFromCurrent);
  459. Result := (ReadCount = SizeOf(ID)) and
  460. CompareMem(@ID, @JpegMagic, SizeOf(JpegMagic)) and
  461. (CompareMem(@ID[6], @JFIFSignature, SizeOf(JFIFSignature)) or
  462. CompareMem(@ID[6], @EXIFSignature, SizeOf(EXIFSignature)));
  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.21 Changes/Bug Fixes -----------------------------------
  476. - MakeCompatible method moved to base class, put ConvertToSupported here.
  477. GetSupportedFormats removed, it is now set in constructor.
  478. - Made public properties for options registered to SetOption/GetOption
  479. functions.
  480. - Changed extensions to filename masks.
  481. - Changed SaveData, LoadData, and MakeCompatible methods according
  482. to changes in base class in Imaging unit.
  483. - Changes in TestFormat, now reads JFIF and EXIF signatures too.
  484. -- 0.19 Changes/Bug Fixes -----------------------------------
  485. - input position is now set correctly to the end of the image
  486. after loading is done. Loading of sequence of JPEG files stored in
  487. single stream works now
  488. - when loading and saving images in FPC with PASJPEG read and
  489. blue channels are swapped to have the same chanel order as IMJPEGLIB
  490. - you can now choose between IMJPEGLIB and PASJPEG implementations
  491. -- 0.17 Changes/Bug Fixes -----------------------------------
  492. - added SetJpegIO method which is used by JNG image format
  493. }
  494. end.