mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-04-25 08:21:27 +00:00
Mon. 20-Nov-2006 Mark Tracy <tracy454 at concentric dot net> Many changes related to bitmap images 1. NSBitmapImateRep attribute global strings were not defined Fix: add definitions to externs.h, and declarations to NSBitmapImageRep.h Comment: Two strings defined in Cocoa were commented out: NSImageColorSyncData is proprietary to Apple, and NSImageEXIFData has no support elsewhere in GNUstep. I propose adding GSImageICCProfileData if and when color management is added to GNUstep. 2. LZW compression in TIFF was disabled for lack of a test of its availability Fix: Implement NSTiffIsCodecConfigured(codec) in tiff.m Comment: As of libtiff-3.7.0, there is a function call in the API to test availability at runtime. For libtiff-3.6.0 (earlier?) there are macros #defined in tiffconf.h. The implementation check the library version at compile time, and uses one of the two methods. I have not tested the second method for lack of an installation of an old libtiff. 3. -canCompressUsing: relied on a static list of capabilities Fix: Use the new NSTiffIsCodecConfigured(codec) in NSBitmapImageRep.m Comment: The static list could be wrong, as it was on my system. Also eliminate the supports_lzw_compression flag. 4. +getTIFFCompressionTypes:count: relied on a static list of compressors. Fix: Use the new NSTiffIsCodecConfigured(codec) in NSBitmapImageRep.m Comment: Compares GNUstep supported compressors against actual availability. Also change the private instance methods _localFromCompressionType and _compressionTypeFromLocal to private class methods so that they can be used in -initWithTIFFImage:number: and -TIFFRepresentationUsingCompression:factor: and +getTIFFCompressionTypes:count: This is probably a clumsy implementation but it works. 5. -setProperty:toValue: and -valueForProperty: were not implemented Fix: Add a new instance variable NSMutableDictionary * _properties to NSBitmapImageRep.h and implemented accessors in NSBitmapImageRep.m. Patch -_initFromTIFFImage to set compression type and factor in _properties. Comment: This feature is used to pass options to and from JPEG, PNG, TIFF, and GIF in Cocoa, although the docs are kind of vague. In one case the Cocoa docs said the properties were set when reading a TIFF, but the implementation didn't; I chose to implement the docs. Cocoa does use properties when exporting bitmaps, so I implemented that. 6. Checked and updated NSBitmapImageFileType in NSBitmapImageRep.h Fix: confirmed the enumeration values against Cocoa, and added NSJPEG2000FileType = 5 Comment: JPEG-2000 is not implemented, just reserved a space for it. 7. -representationUsingType:properties: was not implemented Fix: Implement export of TIFF, JPEG, GIF and PNG in NSBitmapImage.m Comment: See the change notes for JPEG, GIF, and PNG for more. BMP and JPEG-2000 are not implemented; they just log a message to that effect. As apparently Cocoa does it this way, if you pass nil for properties, it falls back to the internal _properties, and if that is empty, there are some safe defaults. 8. +representationfOfImageRepsInArray:UsingType:properties: was not implemented Fix: Partially implement in NSBitmapImageRep.m Comment: I just stole the incomplete code from +TIFFRepresentationOfImageRepsInArray: since I have yet to find an explanation of how this really ought to work. 9. JPEG export didn't handle alpha channel, properties or errors. Fix: Add -_JPEGRepresentationWithProperties:errorMessage: to NSBitmapImageRep+JPEG.h and greatly rework Nicolas Roard's code in NSBitmapImageRep+JPEG.m. Patch -_initBitmapFromJPEG:errorMessage to write properties. Comment: Major rewrite of Nicolas Roard's JPEG export code. To do: Support for planar bitmaps and support for colorspaces other than RGB(A). 10. PNG export not implemented Fix: Add -_PNGRepresentationWithProperties: to NSBitmapImageRep+PNG.h and implement NSBitmapImageRep+PNG.m Comment: No support yet for planar bitmaps. Only supports NS*WhiteColorSpace and NS*RGBColorSpace. Does support alpha. Support for reading and writing NSImageGamma is experimental. In keeping with Cocoa, the property NSImageGamma ranges from 0.0 to 1.0; representing the range of minimum supported gamma to maximum supported gamma, in this case 1.0 to 2.5. This is in contrast to GNUstep where by convention the property would range from 0.0 to 255.0. To do: proper error message support 11. GIF export not implemented Fix: Add -_GIFRepresentationWithPropterties:errorMessage: to NSBitmapImageRep+GIF.h and implement in NSBitmapImageRep+GIF.m Comments: Supports only RGB(A) colorspaces, but ignores alpha. Supports planar or interleaved bitmaps. Supports properties NSImageRGBColorTable. 12. -_initBitmapFromGIF:errorMessage: did not support transparency Fix: Don't ignore control blocks in NSBitmapImageRep+GIF.m; check for transparency. Comment: If a transparent color is found, it adds an alpha channel to the bitmap. Also, save the color table in properties. 13. -_initBitmapFromGIF:errorMessage: would show the last image in a multi-image GIF file Fix: Break the parsing loop after the first image in NSBitmapImageRep+GIF.m Comment: Also check for frame duration, and set that property. There is not yet any support for animated GIF. This will require some additional infrastructure, and I won't do it unless asked. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@24140 72102866-910b-0410-8b05-ffd578937521
671 lines
18 KiB
Objective-C
671 lines
18 KiB
Objective-C
/* NSBitmapImageRep+JPEG.m
|
|
|
|
Methods for reading jpeg images
|
|
|
|
Copyright (C) 2003 Free Software Foundation, Inc.
|
|
|
|
Written by: Stefan Kleine Stegemann <stefan@wms-network.de>
|
|
Date: Nov 2003
|
|
|
|
This file is part of the GNUstep GUI Library.
|
|
|
|
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 library 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; see the file COPYING.LIB.
|
|
If not, write to the Free Software Foundation,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "NSBitmapImageRep+JPEG.h"
|
|
|
|
#if HAVE_LIBJPEG
|
|
|
|
#include <Foundation/NSString.h>
|
|
#include <Foundation/NSData.h>
|
|
#include <Foundation/NSException.h>
|
|
#include <Foundation/NSValue.h>
|
|
#include "AppKit/NSGraphics.h"
|
|
|
|
#include <jerror.h>
|
|
#if defined(__MINGW32__)
|
|
/* Hack so that INT32 is not redefined in jmorecfg.h. MingW defines this
|
|
as well in basetsd.h */
|
|
#ifndef XMD_H
|
|
#define XMD_H
|
|
#endif
|
|
#endif
|
|
#include <jpeglib.h>
|
|
|
|
#include <setjmp.h>
|
|
|
|
|
|
/* -----------------------------------------------------------
|
|
The following functions are for interacting with the
|
|
jpeg library
|
|
----------------------------------------------------------- */
|
|
|
|
/* A custom error manager for the jpeg library
|
|
* that 'inherits' from libjpeg's standard
|
|
* error manager. */
|
|
struct gs_jpeg_error_mgr
|
|
{
|
|
struct jpeg_error_mgr parent;
|
|
|
|
/* marks where to return after an error (instead of
|
|
simply exiting) */
|
|
jmp_buf setjmpBuffer;
|
|
|
|
/* a pointer to the last error message, nil if no
|
|
error occured. if present, string is autoreleased. */
|
|
NSString *error;
|
|
};
|
|
typedef struct gs_jpeg_error_mgr *gs_jpeg_error_mgr_ptr;
|
|
|
|
/* Print the last jpeg library error and returns
|
|
* the control to the caller of the libary.
|
|
* libjpegs default error handling would exit
|
|
* after printing the error. */
|
|
static void gs_jpeg_error_exit(j_common_ptr cinfo)
|
|
{
|
|
gs_jpeg_error_mgr_ptr myerr = (gs_jpeg_error_mgr_ptr)cinfo->err;
|
|
(*cinfo->err->output_message)(cinfo);
|
|
|
|
/* jump back to the caller of the library */
|
|
longjmp(myerr->setjmpBuffer, 1);
|
|
}
|
|
|
|
|
|
/* Save the error message in error. */
|
|
static void gs_jpeg_output_message(j_common_ptr cinfo)
|
|
{
|
|
char msgBuffer[JMSG_LENGTH_MAX];
|
|
|
|
gs_jpeg_error_mgr_ptr myerr = (gs_jpeg_error_mgr_ptr)cinfo->err;
|
|
|
|
(*cinfo->err->format_message)(cinfo, msgBuffer);
|
|
myerr->error = [NSString stringWithCString: msgBuffer];
|
|
}
|
|
|
|
|
|
/* Initialize our error manager */
|
|
static void gs_jpeg_error_mgr_init(gs_jpeg_error_mgr_ptr errMgr)
|
|
{
|
|
errMgr->error = nil;
|
|
}
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------*/
|
|
|
|
/* A custom data source manager that allows the jpeg library
|
|
* to read it's data from memory. It 'inherits' from the
|
|
* default jpeg source manager. */
|
|
typedef struct
|
|
{
|
|
struct jpeg_source_mgr parent;
|
|
|
|
/* the data to be passed to the library functions */
|
|
const unsigned char *data;
|
|
unsigned int length;
|
|
} gs_jpeg_source_mgr;
|
|
|
|
typedef gs_jpeg_source_mgr *gs_jpeg_source_ptr;
|
|
|
|
|
|
static void gs_init_source(j_decompress_ptr cinfo)
|
|
{
|
|
/* nothing to do here (so far) */
|
|
}
|
|
|
|
|
|
static boolean gs_fill_input_buffer(j_decompress_ptr cinfo)
|
|
{
|
|
gs_jpeg_source_ptr src = (gs_jpeg_source_ptr)cinfo->src;
|
|
|
|
/* we make all data available at once */
|
|
if (src->length == 0)
|
|
{
|
|
ERREXIT(cinfo, JERR_INPUT_EMPTY);
|
|
}
|
|
|
|
src->parent.next_input_byte = src->data;
|
|
src->parent.bytes_in_buffer = src->length;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static void gs_skip_input_data(j_decompress_ptr cinfo, long numBytes)
|
|
{
|
|
gs_jpeg_source_ptr src = (gs_jpeg_source_ptr)cinfo->src;
|
|
|
|
if (numBytes > 0)
|
|
{
|
|
src->parent.next_input_byte += numBytes;
|
|
src->parent.bytes_in_buffer -= numBytes;
|
|
}
|
|
}
|
|
|
|
|
|
static void gs_term_source(j_decompress_ptr cinfo)
|
|
{
|
|
/* nothing to do here */
|
|
}
|
|
|
|
|
|
/* Prepare a decompression object for input from memory. The
|
|
* caller is responsible for providing and releasing the
|
|
* data. After decompression is done, gs_jpeg_memory_src_destroy
|
|
* has to be called. */
|
|
static void gs_jpeg_memory_src_create(j_decompress_ptr cinfo, NSData *data)
|
|
{
|
|
gs_jpeg_source_ptr src;
|
|
|
|
cinfo->src = (struct jpeg_source_mgr *)malloc(sizeof(gs_jpeg_source_mgr));
|
|
src = (gs_jpeg_source_ptr)cinfo->src;
|
|
|
|
src = (gs_jpeg_source_ptr) cinfo->src;
|
|
src->parent.init_source = gs_init_source;
|
|
src->parent.fill_input_buffer = gs_fill_input_buffer;
|
|
src->parent.skip_input_data = gs_skip_input_data;
|
|
src->parent.resync_to_restart = jpeg_resync_to_restart; /* use default */
|
|
src->parent.term_source = gs_term_source;
|
|
src->parent.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
|
|
src->parent.next_input_byte = NULL; /* until buffer loaded */
|
|
|
|
src->data = (const unsigned char *)[data bytes];
|
|
src->length = [data length];
|
|
}
|
|
|
|
|
|
/* Destroy the source manager of the jpeg decompression object. */
|
|
static void gs_jpeg_memory_src_destroy(j_decompress_ptr cinfo)
|
|
{
|
|
gs_jpeg_source_ptr src = (gs_jpeg_source_ptr)cinfo->src;
|
|
|
|
free(src); // does not free the data
|
|
cinfo->src = NULL;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------*/
|
|
|
|
/*
|
|
* A custom destination manager.
|
|
*/
|
|
|
|
typedef struct
|
|
{
|
|
struct jpeg_destination_mgr pub; // public fields
|
|
unsigned char* buffer;
|
|
unsigned char* data;
|
|
NSData** finalData;
|
|
int length;
|
|
} gs_jpeg_destination_mgr;
|
|
typedef gs_jpeg_destination_mgr * gs_jpeg_dest_ptr;
|
|
|
|
/*
|
|
Initialize destination. This is called by jpeg_start_compress()
|
|
before any data is actually written. It must initialize
|
|
next_output_byte and free_in_buffer. free_in_buffer must be
|
|
initialized to a positive value.
|
|
*/
|
|
|
|
static void gs_init_destination (j_compress_ptr cinfo)
|
|
{
|
|
//NSLog (@"gs_init_destination");
|
|
gs_jpeg_dest_ptr dest = (gs_jpeg_dest_ptr) cinfo->dest;
|
|
|
|
// allocate the output image
|
|
|
|
int imageSize = cinfo->image_width * cinfo->image_height;
|
|
|
|
dest->buffer = (void*) calloc ((imageSize * cinfo->input_components),
|
|
sizeof(unsigned char));
|
|
|
|
dest->data = (void*) calloc ((imageSize * cinfo->input_components),
|
|
sizeof(unsigned char));
|
|
|
|
dest->pub.next_output_byte = dest->buffer;
|
|
dest->pub.free_in_buffer = imageSize * cinfo->input_components;
|
|
dest->length = 0;
|
|
}
|
|
|
|
/*
|
|
This is called whenever the buffer has filled (free_in_buffer
|
|
reaches zero). In typical applications, it should write out the
|
|
*entire* buffer (use the saved start address and buffer length;
|
|
ignore the current state of next_output_byte and free_in_buffer).
|
|
Then reset the pointer & count to the start of the buffer, and
|
|
return TRUE indicating that the buffer has been dumped.
|
|
free_in_buffer must be set to a positive value when TRUE is
|
|
returned. A FALSE return should only be used when I/O suspension is
|
|
desired (this operating mode is discussed in the next section).
|
|
*/
|
|
|
|
static boolean gs_empty_output_buffer (j_compress_ptr cinfo)
|
|
{
|
|
|
|
//NSLog (@"gs_empty_output_buffer...");
|
|
gs_jpeg_dest_ptr dest = (gs_jpeg_dest_ptr) cinfo->dest;
|
|
//NSLog (@"length added (%d)", dest->length);
|
|
int imageSize = cinfo->image_width * cinfo->image_height;
|
|
int bufSize = imageSize * cinfo->input_components;
|
|
int i;
|
|
|
|
for (i = 0; i < bufSize; i++)
|
|
{
|
|
dest->data [dest->length + i] = dest->buffer [i];
|
|
}
|
|
|
|
dest->length = dest->length + bufSize;
|
|
dest->pub.next_output_byte = dest->buffer;
|
|
dest->pub.free_in_buffer = imageSize * cinfo->input_components;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
Terminate destination --- called by jpeg_finish_compress() after all
|
|
data has been written. In most applications, this must flush any
|
|
data remaining in the buffer. Use either next_output_byte or
|
|
free_in_buffer to determine how much data is in the buffer.
|
|
*/
|
|
|
|
static void gs_term_destination (j_compress_ptr cinfo)
|
|
{
|
|
//NSLog (@"gs_term_destination");
|
|
gs_jpeg_dest_ptr dest = (gs_jpeg_dest_ptr) cinfo->dest;
|
|
int imageSize = cinfo->image_width * cinfo->image_height;
|
|
int bufSize = imageSize * cinfo->input_components;
|
|
int i;
|
|
|
|
for (i = 0; i < bufSize; i++)
|
|
{
|
|
dest->data [dest->length + i] = dest->buffer [i];
|
|
}
|
|
dest->length = dest->length + bufSize;
|
|
|
|
*dest->finalData = [[NSData alloc] initWithBytes: dest->data
|
|
length: (dest->length) - dest->pub.free_in_buffer];
|
|
}
|
|
|
|
static void gs_jpeg_memory_dest_create (j_compress_ptr cinfo, NSData** data)
|
|
{
|
|
gs_jpeg_dest_ptr dest;
|
|
|
|
cinfo->dest = (struct jpeg_destination_mgr*)
|
|
malloc (sizeof (gs_jpeg_destination_mgr));
|
|
|
|
dest = (gs_jpeg_dest_ptr) cinfo->dest;
|
|
|
|
dest->pub.init_destination = gs_init_destination;
|
|
dest->pub.empty_output_buffer = gs_empty_output_buffer;
|
|
dest->pub.term_destination = gs_term_destination;
|
|
dest->finalData = data;
|
|
}
|
|
|
|
static void gs_jpeg_memory_dest_destroy (j_compress_ptr cinfo)
|
|
{
|
|
gs_jpeg_dest_ptr dest = (gs_jpeg_dest_ptr) cinfo->dest;
|
|
free (dest->buffer);
|
|
free (dest->data);
|
|
free (dest);
|
|
cinfo->dest = NULL;
|
|
}
|
|
|
|
|
|
/* -----------------------------------------------------------
|
|
The jpeg loading part of NSBitmapImageRep
|
|
----------------------------------------------------------- */
|
|
|
|
@implementation NSBitmapImageRep (JPEGReading)
|
|
|
|
|
|
/* Return YES if this looks like a JPEG. */
|
|
+ (BOOL) _bitmapIsJPEG: (NSData *)imageData
|
|
{
|
|
struct jpeg_decompress_struct cinfo;
|
|
struct gs_jpeg_error_mgr jerrMgr;
|
|
|
|
/* Be sure imageData contains data */
|
|
if (![imageData length])
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
/* Establish the our custom error handler */
|
|
gs_jpeg_error_mgr_init(&jerrMgr);
|
|
cinfo.err = jpeg_std_error(&jerrMgr.parent);
|
|
jerrMgr.parent.error_exit = gs_jpeg_error_exit;
|
|
jerrMgr.parent.output_message = gs_jpeg_output_message;
|
|
|
|
// establish return context for error handling
|
|
if (setjmp(jerrMgr.setjmpBuffer))
|
|
{
|
|
gs_jpeg_memory_src_destroy(&cinfo);
|
|
jpeg_destroy_decompress(&cinfo);
|
|
return NO;
|
|
}
|
|
|
|
/* read the header to see if we have a jpeg */
|
|
jpeg_create_decompress(&cinfo);
|
|
|
|
/* Establish our own data source manager */
|
|
gs_jpeg_memory_src_create(&cinfo, imageData);
|
|
|
|
jpeg_read_header(&cinfo, TRUE);
|
|
gs_jpeg_memory_src_destroy(&cinfo);
|
|
jpeg_destroy_decompress(&cinfo);
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
/* Read the jpeg image. Assume it is from a jpeg file and imageData
|
|
* is not nil.
|
|
*/
|
|
- (id) _initBitmapFromJPEG: (NSData *)imageData
|
|
errorMessage: (NSString **)errorMsg
|
|
{
|
|
struct jpeg_decompress_struct cinfo;
|
|
struct gs_jpeg_error_mgr jerrMgr;
|
|
JDIMENSION sclcount, samplesPerRow, i, j, rowSize;
|
|
JSAMPARRAY sclbuffer = NULL;
|
|
unsigned char *imgbuffer = NULL;
|
|
BOOL isProgressive;
|
|
|
|
if (!(self = [super init]))
|
|
return nil;
|
|
|
|
/* Establish the our custom error handler */
|
|
gs_jpeg_error_mgr_init(&jerrMgr);
|
|
cinfo.err = jpeg_std_error(&jerrMgr.parent);
|
|
jerrMgr.parent.error_exit = gs_jpeg_error_exit;
|
|
jerrMgr.parent.output_message = gs_jpeg_output_message;
|
|
|
|
// establish return context for error handling
|
|
if (setjmp(jerrMgr.setjmpBuffer))
|
|
{
|
|
/* assign the description of possible occured error to errorMsg */
|
|
if (errorMsg)
|
|
*errorMsg = (jerrMgr.error ? (id)jerrMgr.error : (id)nil);
|
|
gs_jpeg_memory_src_destroy(&cinfo);
|
|
jpeg_destroy_decompress(&cinfo);
|
|
if (imgbuffer)
|
|
{
|
|
free(imgbuffer);
|
|
}
|
|
RELEASE(self);
|
|
return nil;
|
|
}
|
|
|
|
/* jpeg-decompression */
|
|
|
|
jpeg_create_decompress(&cinfo);
|
|
|
|
/* Establish our own data source manager */
|
|
gs_jpeg_memory_src_create(&cinfo, imageData);
|
|
|
|
jpeg_read_header(&cinfo, TRUE);
|
|
|
|
/* we use RGB as target color space; others are not yet supported */
|
|
cinfo.out_color_space = JCS_RGB;
|
|
|
|
/* decompress */
|
|
jpeg_start_decompress(&cinfo);
|
|
|
|
/* process the decompressed data */
|
|
samplesPerRow = cinfo.output_width * cinfo.output_components;
|
|
rowSize = samplesPerRow * sizeof(unsigned char);
|
|
NSAssert(sizeof(JSAMPLE) == sizeof(unsigned char),
|
|
@"unexpected sample size");
|
|
|
|
sclbuffer = cinfo.mem->alloc_sarray((j_common_ptr)&cinfo,
|
|
JPOOL_IMAGE,
|
|
samplesPerRow,
|
|
cinfo.rec_outbuf_height);
|
|
/* sclbuffer is freed when cinfo is destroyed */
|
|
|
|
imgbuffer = NSZoneMalloc([self zone], cinfo.output_height * rowSize);
|
|
if (!imgbuffer)
|
|
{
|
|
NSLog(@"NSBitmapImageRep+JPEG: failed to allocated image buffer");
|
|
RELEASE(self);
|
|
return nil;
|
|
}
|
|
|
|
i = 0;
|
|
while (cinfo.output_scanline < cinfo.output_height)
|
|
{
|
|
sclcount
|
|
= jpeg_read_scanlines(&cinfo, sclbuffer, cinfo.rec_outbuf_height);
|
|
|
|
for (j = 0; j < sclcount; j++)
|
|
{
|
|
// copy a row to the image buffer
|
|
memcpy((imgbuffer + (i * rowSize)),
|
|
*(sclbuffer + (j * rowSize)),
|
|
rowSize);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
isProgressive = cinfo.progressive_mode;
|
|
|
|
/* done */
|
|
jpeg_finish_decompress(&cinfo);
|
|
|
|
gs_jpeg_memory_src_destroy(&cinfo);
|
|
jpeg_destroy_decompress(&cinfo);
|
|
|
|
if (jerrMgr.parent.num_warnings)
|
|
{
|
|
NSLog(@"NSBitmapImageRep+JPEG: %d warnings during jpeg decompression, "
|
|
@"image may be corrupted", jerrMgr.parent.num_warnings);
|
|
}
|
|
|
|
// create the imagerep
|
|
//BITS_IN_JSAMPLE is defined by libjpeg
|
|
[self initWithBitmapDataPlanes: &imgbuffer
|
|
pixelsWide: cinfo.output_width
|
|
pixelsHigh: cinfo.output_height
|
|
bitsPerSample: BITS_IN_JSAMPLE
|
|
samplesPerPixel: cinfo.output_components
|
|
hasAlpha: (cinfo.output_components == 3 ? NO : YES)
|
|
isPlanar: NO
|
|
colorSpaceName: NSCalibratedRGBColorSpace
|
|
bytesPerRow: rowSize
|
|
bitsPerPixel: BITS_IN_JSAMPLE * cinfo.output_components];
|
|
|
|
[self setProperty: NSImageProgressive
|
|
withValue: [NSNumber numberWithBool: isProgressive]];
|
|
|
|
_imageData = [[NSData alloc]
|
|
initWithBytesNoCopy: imgbuffer
|
|
length: (rowSize * cinfo.output_height)];
|
|
|
|
return self;
|
|
}
|
|
|
|
|
|
/* -----------------------------------------------------------
|
|
The jpeg writing part of NSBitmapImageRep
|
|
----------------------------------------------------------- */
|
|
|
|
- (NSData*) _JPEGRepresentationWithProperties: (NSDictionary*) properties
|
|
errorMessage: (NSString **)errorMsg
|
|
{
|
|
NSData *ret;
|
|
unsigned char *imageSource;
|
|
int sPP;
|
|
int width;
|
|
int height;
|
|
int row_stride;
|
|
int quality = 90;
|
|
NSNumber *qualityNumber = nil;
|
|
NSNumber *progressiveNumber = nil;
|
|
NSString *colorSpace = nil;
|
|
BOOL isRGB;
|
|
struct jpeg_compress_struct cinfo;
|
|
struct gs_jpeg_error_mgr jerrMgr;
|
|
JSAMPROW row_pointer[1]; // pointer to a single row
|
|
|
|
// TODO: handles planar images
|
|
|
|
if ([self isPlanar])
|
|
{
|
|
NSString * em = @"JPEG image rep: Planar Image, not handled yet !";
|
|
if (errorMsg != NULL)
|
|
*errorMsg = em;
|
|
else
|
|
NSLog (em);
|
|
return nil;
|
|
}
|
|
|
|
imageSource = [self bitmapData];
|
|
sPP = [self samplesPerPixel];
|
|
width = [self size].width;
|
|
height = [self size].height;
|
|
row_stride = width * sPP;
|
|
|
|
/* Establish the our custom error handler */
|
|
gs_jpeg_error_mgr_init(&jerrMgr);
|
|
cinfo.err = jpeg_std_error(&jerrMgr.parent);
|
|
jerrMgr.parent.error_exit = gs_jpeg_error_exit;
|
|
jerrMgr.parent.output_message = gs_jpeg_output_message;
|
|
|
|
// establish return context for error handling
|
|
if (setjmp(jerrMgr.setjmpBuffer))
|
|
{
|
|
/* assign the description of possible occured error to errorMsg */
|
|
if (errorMsg)
|
|
*errorMsg = (jerrMgr.error ? (id)jerrMgr.error : (id)nil);
|
|
gs_jpeg_memory_dest_destroy(&cinfo);
|
|
jpeg_destroy_compress(&cinfo);
|
|
return nil;
|
|
}
|
|
|
|
// initialize libjpeg for compression
|
|
|
|
jpeg_create_compress (&cinfo);
|
|
|
|
// specify the destination for the compressed data..
|
|
|
|
gs_jpeg_memory_dest_create (&cinfo, &ret);
|
|
|
|
// set parameters
|
|
|
|
colorSpace = [self colorSpaceName];
|
|
isRGB = ([colorSpace isEqualToString: NSDeviceRGBColorSpace]
|
|
|| [colorSpace isEqualToString: NSCalibratedRGBColorSpace]);
|
|
cinfo.image_width = width;
|
|
cinfo.image_height = height;
|
|
// note we will strip alpha from RGBA
|
|
cinfo.input_components = (isRGB && [self hasAlpha])? 3 : sPP;
|
|
cinfo.in_color_space = JCS_UNKNOWN;
|
|
if (isRGB) cinfo.in_color_space = JCS_RGB;
|
|
if (sPP == 1) cinfo.in_color_space = JCS_GRAYSCALE;
|
|
if ([colorSpace isEqualToString: NSDeviceCMYKColorSpace])
|
|
cinfo.in_color_space = JCS_CMYK;
|
|
if (cinfo.in_color_space == JCS_UNKNOWN)
|
|
NSLog(@"JPEG image rep: Using unknown color space with unpredictable results");
|
|
|
|
jpeg_set_defaults (&cinfo);
|
|
|
|
// set quality
|
|
|
|
qualityNumber = [properties objectForKey: NSImageCompressionFactor];
|
|
if (qualityNumber != nil)
|
|
{
|
|
quality = (int) ((1-[qualityNumber floatValue] / 255.0) * 100.0);
|
|
}
|
|
|
|
// set progressive mode
|
|
progressiveNumber = [properties objectForKey: NSImageProgressive];
|
|
if (progressiveNumber != nil)
|
|
{
|
|
cinfo.progressive_mode = [progressiveNumber boolValue];
|
|
}
|
|
|
|
|
|
// compress the image
|
|
|
|
jpeg_set_quality (&cinfo, quality, TRUE);
|
|
jpeg_start_compress (&cinfo, TRUE);
|
|
|
|
if (isRGB && [self hasAlpha]) // strip alpha channel before encoding
|
|
{
|
|
unsigned char * RGB, * pRGB, * pRGBA;
|
|
unsigned int iRGB, iRGBA;
|
|
OBJC_MALLOC(RGB, unsigned char, 3*width);
|
|
while (cinfo.next_scanline < cinfo.image_height)
|
|
{
|
|
iRGBA = cinfo.next_scanline * row_stride;
|
|
pRGBA = &imageSource[iRGBA];
|
|
pRGB = RGB;
|
|
for (iRGB = 0; iRGB < 3*width; iRGB += 3)
|
|
{
|
|
memcpy(pRGB, pRGBA, 3);
|
|
pRGB +=3;
|
|
pRGBA +=4;
|
|
}
|
|
row_pointer[0] = RGB;
|
|
jpeg_write_scanlines (&cinfo, row_pointer, 1);
|
|
}
|
|
OBJC_FREE(RGB);
|
|
}
|
|
else // no alpha channel
|
|
{
|
|
while (cinfo.next_scanline < cinfo.image_height)
|
|
{
|
|
int index = cinfo.next_scanline * row_stride;
|
|
|
|
row_pointer[0] = &imageSource[index];
|
|
jpeg_write_scanlines (&cinfo, row_pointer, 1);
|
|
}
|
|
}
|
|
|
|
jpeg_finish_compress(&cinfo);
|
|
|
|
gs_jpeg_memory_dest_destroy (&cinfo);
|
|
|
|
jpeg_destroy_compress(&cinfo);
|
|
|
|
return AUTORELEASE(ret);
|
|
}
|
|
|
|
@end
|
|
|
|
#else /* !HAVE_LIBJPEG */
|
|
|
|
@implementation NSBitmapImageRep (JPEGReading)
|
|
+ (BOOL) _bitmapIsJPEG: (NSData *)imageData
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (id) _initBitmapFromJPEG: (NSData *)imageData
|
|
errorMessage: (NSString **)errorMsg
|
|
{
|
|
RELEASE(self);
|
|
return nil;
|
|
}
|
|
- (NSData *) _JPEGRepresentationWithProperties: (NSDictionary *) properties
|
|
errorMessage: (NSString **)errorMsg
|
|
{
|
|
return nil;
|
|
}
|
|
@end
|
|
|
|
#endif /* !HAVE_LIBJPEG */
|
|
|