Statistics
| Branch: | Tag: | Revision:

root / Imaging / ImagingJpeg.pas @ 0:95bd93c28625

History | View | Annotate | Download (17.1 kB)

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