#include "Epeg.h" #include "epeg_private.h" #include static Epeg_Image *_epeg_open_header (Epeg_Image *im); static int _epeg_decode (Epeg_Image *im); static int _epeg_scale (Epeg_Image *im); static int _epeg_decode_for_trim (Epeg_Image *im); static int _epeg_trim (Epeg_Image *im); static int _epeg_encode (Epeg_Image *im); static void _epeg_fatal_error_handler (j_common_ptr cinfo); #define MIN(__x,__y) ((__x) < (__y) ? (__x) : (__y)) #define MAX(__x,__y) ((__x) > (__y) ? (__x) : (__y)) static const JOCTET fake_EOI[2] = { 0xFF, JPEG_EOI }; /** * Open a JPEG image by filename. * @param file The file path to open. * @return A handle to the opened JPEG file, with the header decoded. * * This function opens the file indicated by the @p file parameter, and * attempts to decode it as a jpeg file. If this failes, NULL is returned. * Otherwise a valid handle to an open JPEG file is returned that can be used * by other Epeg calls. * * The @p file must be a pointer to a valid C string, NUL (0 byte) terminated * thats is a relative or absolute file path. If not results are not * determined. * * See also: epeg_memory_open(), epeg_close() */ EAPI Epeg_Image * epeg_file_open(const char *file) { Epeg_Image *im; im = calloc(1, sizeof(Epeg_Image)); if (!im) return NULL; im->in.file = strdup(file); if (!im->in.file) { free(im); return NULL; } im->in.f = fopen(im->in.file, "rb"); if (!im->in.f) { epeg_close(im); return NULL; } fstat(fileno(im->in.f), &(im->stat_info)); im->out.quality = 75; return _epeg_open_header(im); } /** * Open a JPEG image stored in memory. * @param data A pointer to the memory containing the JPEG data. * @param size The size of the memory segment containing the JPEG. * @return A handle to the opened JPEG, with the header decoded. * * This function opens a JPEG file that is stored in memory pointed to by * @p data, and that is @p size bytes in size. If successful a valid handle * is returned, or on failure NULL is returned. * * See also: epeg_file_open(), epeg_close() */ EAPI EAPI Epeg_Image * epeg_memory_open(unsigned char *data, int size) { Epeg_Image *im; im = calloc(1, sizeof(Epeg_Image)); if (!im) return NULL; im->out.quality = 75; im->in.mem.data = (unsigned char **)data; im->in.mem.size = size; im->in.f = NULL; im->in.w = 0; im->in.h = 0; return _epeg_open_header(im); } /** * Return the original JPEG pixel size. * @param im A handle to an opened Epeg image. * @param w A pointer to the width value in pixels to be filled in. * @param h A pointer to the height value in pixels to be filled in. * * Returns the image size in pixels. * */ EAPI void epeg_size_get(Epeg_Image *im, int *w, int *h) { if (w) *w = im->in.w; if (h) *h = im->in.h; } /** * Return the original JPEG pixel color space. * @param im A handle to an opened Epeg image. * @param space A pointer to the color space value to be filled in. * * Returns the image color space. * */ EAPI void epeg_colorspace_get(Epeg_Image *im, int *space) { if (space) *space = im->color_space; } /** * Set the size of the image to decode in pixels. * @param im A handle to an opened Epeg image. * @param w The width of the image to decode at, in pixels. * @param h The height of the image to decode at, in pixels. * * Sets the size at which to deocode the JPEG image, giving an optimised load * that only decodes the pixels needed. * */ EAPI void epeg_decode_size_set(Epeg_Image *im, int w, int h) { if (im->pixels) return; if (w < 1) w = 1; else if (w > im->in.w) w = im->in.w; if (h < 1) h = 1; else if (h > im->in.h) h = im->in.h; im->out.w = w; im->out.h = h; im->out.x = 0; im->out.y = 0; } EAPI void epeg_decode_bounds_set(Epeg_Image *im, int x, int y, int w, int h) { if (im->pixels) return; if (w < 1) w = 1; else if (w > im->in.w) w = im->in.w; if (h < 1) h = 1; else if (h > im->in.h) h = im->in.h; im->out.w = w; im->out.h = h; if (x < 0) x = 0; if (y < 0) y = 0; im->out.x = x; im->out.y = y; } /** * Set the colorspace in which to decode the image. * @param im A handle to an opened Epeg image. * @param colorspace The colorspace to decode the image in. * * This sets the colorspace to decode the image in. The default is EPEG_YUV8, * as this is normally the native colorspace of a JPEG file, avoiding any * colorspace conversions for a faster load and/or save. */ EAPI void epeg_decode_colorspace_set(Epeg_Image *im, Epeg_Colorspace colorspace) { if (im->pixels) return; if ((colorspace < EPEG_GRAY8) || (colorspace > EPEG_CMYK)) return; im->color_space = colorspace; } /** * Get a segment of decoded pixels from an image. * @param im A handle to an opened Epeg image. * @param x Rectangle X. * @param y Rectangle Y. * @param w Rectangle width. * @param h Rectangle height. * @return Pointer to the top left of the requested pixel block. * * Return image pixels in the decoded format from the specified location * rectangle bounded with the box @p x, @p y @p w X @p y. The pixel block is * packed with no row padding, and it organsied from top-left to bottom right, * row by row. You must free the pixel block using epeg_pixels_free() before * you close the image handle, and assume the pixels to be read-only memory. * * On success the pointer is returned, on failure, NULL is returned. Failure * may be because the rectangle is out of the bounds of the image, memory * allocations failed or the image data cannot be decoded. * */ EAPI const void * epeg_pixels_get(Epeg_Image *im, int x, int y, int w, int h) { int xx, yy, ww, hh, bpp, ox, oy, ow, oh, iw, ih; if (!im->pixels) { if (_epeg_decode(im) != 0) return NULL; } if (!im->pixels) return NULL; if ((im->out.w < 1) || (im->out.h < 1)) return NULL; if (_epeg_scale(im) != 0) return NULL; bpp = im->in.jinfo.output_components; iw = im->out.w; ih = im->out.h; ow = w; oh = h; ox = 0; oy = 0; if ((x + ow) > iw) ow = iw - x; if ((y + oh) > ih) oh = ih - y; if (ow < 1) return NULL; if (oh < 1) return NULL; if (x < 0) { ow += x; ox = -x; } if (y < 0) { oh += y; oy = -y; } if (ow < 1) return NULL; if (oh < 1) return NULL; ww = x + ox + ow; hh = y + oy + oh; if (im->color_space == EPEG_GRAY8) { unsigned char *pix, *p; pix = malloc(w * h * 1); if (!pix) return NULL; for (yy = y + oy; yy < hh; yy++) { unsigned char *s; s = im->lines[yy] + ((x + ox) * bpp); p = pix + ((((yy - y) * w) + ox)); for (xx = x + ox; xx < ww; xx++) { p[0] = s[0]; p++; s += bpp; } } return pix; } else if (im->color_space == EPEG_YUV8) { unsigned char *pix, *p; pix = malloc(w * h * 3); if (!pix) return NULL; for (yy = y + oy; yy < hh; yy++) { unsigned char *s; s = im->lines[yy] + ((x + ox) * bpp); p = pix + ((((yy - y) * w) + ox) * 3); for (xx = x + ox; xx < ww; xx++) { p[0] = s[0]; p[1] = s[1]; p[2] = s[2]; p += 3; s += bpp; } } return pix; } else if (im->color_space == EPEG_RGB8) { unsigned char *pix, *p; pix = malloc(w * h * 3); if (!pix) return NULL; for (yy = y + oy; yy < hh; yy++) { unsigned char *s; s = im->lines[yy] + ((x + ox) * bpp); p = pix + ((((yy - y) * w) + ox) * 3); for (xx = x + ox; xx < ww; xx++) { p[0] = s[0]; p[1] = s[1]; p[2] = s[2]; p += 3; s += bpp; } } return pix; } else if (im->color_space == EPEG_BGR8) { unsigned char *pix, *p; pix = malloc(w * h * 3); if (!pix) return NULL; for (yy = y + oy; yy < hh; yy++) { unsigned char *s; s = im->lines[yy] + ((x + ox) * bpp); p = pix + ((((yy - y) * w) + ox) * 3); for (xx = x + ox; xx < ww; xx++) { p[0] = s[2]; p[1] = s[1]; p[2] = s[0]; p += 3; s += bpp; } } return pix; } else if (im->color_space == EPEG_RGBA8) { unsigned char *pix, *p; pix = malloc(w * h * 4); if (!pix) return NULL; for (yy = y + oy; yy < hh; yy++) { unsigned char *s; s = im->lines[yy] + ((x + ox) * bpp); p = pix + ((((yy - y) * w) + ox) * 4); for (xx = x + ox; xx < ww; xx++) { p[0] = s[0]; p[1] = s[1]; p[2] = s[2]; p[3] = 0xff; p += 4; s += bpp; } } return pix; } else if (im->color_space == EPEG_BGRA8) { unsigned char *pix, *p; pix = malloc(w * h * 4); if (!pix) return NULL; for (yy = y + oy; yy < hh; yy++) { unsigned char *s; s = im->lines[yy] + ((x + ox) * bpp); p = pix + ((((yy - y) * w) + ox) * 4); for (xx = x + ox; xx < ww; xx++) { p[0] = 0xff; p[1] = s[2]; p[2] = s[1]; p[3] = s[0]; p += 4; s += bpp; } } return pix; } else if (im->color_space == EPEG_ARGB32) { unsigned int *pix, *p; pix = malloc(w * h * 4); if (!pix) return NULL; for (yy = y + oy; yy < hh; yy++) { unsigned char *s; s = im->lines[yy] + ((x + ox) * bpp); p = pix + ((((yy - y) * w) + ox)); for (xx = x + ox; xx < ww; xx++) { p[0] = 0xff000000 | (s[0] << 16) | (s[1] << 8) | (s[2]); p++; s += bpp; } } return pix; } else if (im->color_space == EPEG_CMYK) { unsigned char *pix, *p; pix = malloc(w * h * 4); if (!pix) return NULL; for (yy = y + oy; yy < hh; yy++) { unsigned char *s; s = im->lines[yy] + ((x + ox) * bpp); p = pix + ((((yy - y) * w) + ox) * 4); for (xx = x + ox; xx < ww; xx++) { p[0] = s[0]; p[1] = s[1]; p[2] = s[2]; p[3] = 0xff; p += 4; s += bpp; } } return pix; } return NULL; } /** * Get a segment of decoded pixels from an image. * @param im A handle to an opened Epeg image. * @param x Rectangle X. * @param y Rectangle Y. * @param w Rectangle width. * @param h Rectangle height. * @return Pointer to the top left of the requested pixel block. * * Return image pixels in the decoded format from the specified location * rectangle bounded with the box @p x, @p y @p w X @p y. The pixel block is * packed with no row padding, and it organsied from top-left to bottom right, * row by row. You must free the pixel block using epeg_pixels_free() before * you close the image handle, and assume the pixels to be read-only memory. * * On success the pointer is returned, on failure, NULL is returned. Failure * may be because the rectangle is out of the bounds of the image, memory * allocations failed or the image data cannot be decoded. * */ EAPI const void * epeg_pixels_get_as_RGB8(Epeg_Image *im, int x, int y, int w, int h) { int xx, yy, ww, hh, bpp, ox, oy, ow, oh, iw, ih; if (!im->pixels) { if (_epeg_decode(im) != 0) return NULL; } if (!im->pixels) return NULL; if ((im->out.w < 1) || (im->out.h < 1)) return NULL; bpp = im->in.jinfo.output_components; iw = im->out.w; ih = im->out.h; ow = w; oh = h; ox = 0; oy = 0; if ((x + ow) > iw) ow = iw - x; if ((y + oh) > ih) oh = ih - y; if (ow < 1) return NULL; if (oh < 1) return NULL; if (x < 0) { ow += x; ox = -x; } if (y < 0) { oh += y; oy = -y; } if (ow < 1) return NULL; if (oh < 1) return NULL; ww = x + ox + ow; hh = y + oy + oh; if (im->color_space == EPEG_GRAY8) { unsigned char *pix, *p; pix = malloc(w * h * 3); if (!pix) return NULL; for (yy = y + oy; yy < hh; yy++) { unsigned char *s; s = im->lines[yy] + ((x + ox) * bpp); p = pix + ((((yy - y) * w) + ox) * 3); for (xx = x + ox; xx < ww; xx++) { p[0] = s[0]; p[1] = s[0]; p[2] = s[0]; p += 3; s += bpp; } } return pix; } if (im->color_space == EPEG_RGB8) { unsigned char *pix, *p; pix = malloc(w * h * 3); if (!pix) return NULL; for (yy = y + oy; yy < hh; yy++) { unsigned char *s; s = im->lines[yy] + ((x + ox) * bpp); p = pix + ((((yy - y) * w) + ox) * 3); for (xx = x + ox; xx < ww; xx++) { p[0] = s[0]; p[1] = s[1]; p[2] = s[2]; p += 3; s += bpp; } } return pix; } if (im->color_space == EPEG_CMYK) { unsigned char *pix, *p; pix = malloc(w * h * 3); if (!pix) return NULL; for (yy = y + oy; yy < hh; yy++) { unsigned char *s; s = im->lines[yy] + ((x + ox) * bpp); p = pix + ((((yy - y) * w) + ox) * 3); for (xx = x + ox; xx < ww; xx++) { p[0] = (unsigned char)(MIN(255, (s[0] * s[3]) / 255)); p[1] = (unsigned char)(MIN(255, (s[1] * s[3]) / 255)); p[2] = (unsigned char)(MIN(255, (s[2] * s[3]) / 255)); p += 3; s += bpp; } } return pix; } return NULL; } /** * Free requested pixel block from an image. * @param im A handle to an opened Epeg image. * @param data The pointer to the image pixels. * * This frees the data for a block of pixels requested from image @p im. * @p data must be a valid (non NULL) pointer to a pixel block taken from the * image @p im by epeg_pixels_get() and mustbe called before the image is * closed by epeg_close(). */ EAPI void epeg_pixels_free(Epeg_Image *im, const void *data) { free((void *)data); } /** * Get the image comment field as a string. * @param im A handle to an opened Epeg image. * @return A pointer to the loaded image comments. * * This function returns the comment field as a string (NUL byte terminated) * of the loaded image @p im, if there is a comment, or NULL if no comment is * saved with the image. Consider the string returned to be read-only. * */ EAPI const char * epeg_comment_get(Epeg_Image *im) { return im->in.comment; } /** * Get thumbnail comments of loaded image. * @param im A handle to an opened Epeg image. * @param info Pointer to a thumbnail info struct to be filled in. * * This function retrieves thumbnail comments written by Epeg to any saved * JPEG files. If no thumbnail comments were saved, the fields will be 0 in * the @p info struct on return. * */ EAPI void epeg_thumbnail_comments_get(Epeg_Image *im, Epeg_Thumbnail_Info *info) { if (!info) return; info->uri = im->in.thumb_info.uri; info->mtime = im->in.thumb_info.mtime; info->w = im->in.thumb_info.w; info->h = im->in.thumb_info.h; info->mimetype = im->in.thumb_info.mime; } /** * Set the comment field of the image for saving. * @param im A handle to an opened Epeg image. * @param comment The comment to set. * * Set the comment for the image file for when it gets saved. This is a NUL * byte terminated C string. If @p comment is NULL the output file will have * no comment field. * * The default comment will be any comment loaded from the input file. * */ EAPI void epeg_comment_set(Epeg_Image *im, const char *comment) { if (im->out.comment) free(im->out.comment); if (!comment) im->out.comment = NULL; else im->out.comment = strdup(comment); } /** * Set the encoding quality of the saved image. * @param im A handle to an opened Epeg image. * @param quality The quality of encoding from 0 to 100. * * Set the quality of the output encoded image. Values from 0 to 100 * inclusive are valid, with 100 being the maximum quality, and 0 being the * minimum. If the quality is set equal to or above 90%, the output U and V * color planes are encoded at 1:1 with the Y plane. * * The default quality is 75. * */ EAPI void epeg_quality_set(Epeg_Image *im, int quality) { if (quality < 0) quality = 0; else if (quality > 100) quality = 100; im->out.quality = quality; } /** * Enable thumbnail comments in saved image. * @param im A handle to an opened Epeg image. * @param onoff A boolean on and off enabling flag. * * if @p onoff is 1, the output file will have thumbnail comments added to * it, and if it is 0, it will not. The default is 0. * */ EAPI void epeg_thumbnail_comments_enable(Epeg_Image *im, int onoff) { im->out.thumbnail_info = onoff; } /** * Set the output file path for the image when saved. * @param im A handle to an opened Epeg image. * @param file The path to the output file. * * This sets the output file path name (either a full or relative path name) * to where the file will be written when saved. @p file must be a NUL * terminated C string conatining the path to the file to be saved to. If it is * NULL, the image will not be saved to a file when calling epeg_encode(). */ EAPI void epeg_file_output_set(Epeg_Image *im, const char *file) { if (im->out.file) free(im->out.file); if (!file) im->out.file = NULL; else im->out.file = strdup(file); } /** * Set the output file to be a block of allocated memory. * @param im A handle to an opened Epeg image. * @param data A pointer to a pointer to a memory block. * @param size A pointer to a counter of the size of the memory block. * * This sets the output encoding of the image when saved to be allocated * memory. After epeg_close() is called the pointer pointed to by @p data * and the integer pointed to by @p size will contain the pointer to the * memory block and its size in bytes, respecitvely. The memory block can be * freed with the free() function call. If the save fails the pointer to the * memory block will be unaffected, as will the size. * */ EAPI void epeg_memory_output_set(Epeg_Image *im, unsigned char **data, int *size) { im->out.mem.data = data; im->out.mem.size = size; im->out.file = NULL; } /** * This saves the image to its specified destination. * @param im A handle to an opened Epeg image. * * This saves the image @p im to its destination specified by * epeg_file_output_set() or epeg_memory_output_set(). The image will be * encoded at the deoded pixel size, using the quality, comment and thumbnail * comment settings set on the image. * * retval 1 - error scale * 2 - error encode * 3 - error decode * 4 - error decode ( setjmp ) */ EAPI int epeg_encode(Epeg_Image *im) { int ret; if ((ret = _epeg_decode(im)) != 0) return (ret == 2 ? 4 : 3); if (_epeg_scale(im) != 0) return 1; if (_epeg_encode(im) != 0) return 2; return 0; } /** * FIXME: Document this * @param im A handle to an opened Epeg image. * * FIXME: Document this. */ EAPI int epeg_trim(Epeg_Image *im) { if (_epeg_decode_for_trim(im) != 0) return 1; if (_epeg_trim(im) != 0) return 1; if (_epeg_encode(im) != 0) return 1; return 0; } /** * Close an image handle. * @param im A handle to an opened Epeg image. * * This closes an opened image handle and frees all memory associated with it. * It does not free encoded data generated by epeg_memory_output_set() followed * by epeg_encode() nor does it guarantee to free any data recieved by * epeg_pixels_get(). Once an image handle is closed consider it invalid. */ EAPI void epeg_close(Epeg_Image *im) { if (!im) return; if (im->pixels) free(im->pixels); if (im->lines) free(im->lines); if (im->in.file) free(im->in.file); if (!im->in.file) free(im->in.jinfo.src); if (im->in.f || im->in.mem.data) jpeg_destroy_decompress(&(im->in.jinfo)); if (im->in.f) fclose(im->in.f); if (im->in.comment) free(im->in.comment); if (im->in.thumb_info.uri) free(im->in.thumb_info.uri); if (im->in.thumb_info.mime) free(im->in.thumb_info.mime); if (im->out.file) free(im->out.file); if (!im->out.file) free(im->out.jinfo.dest); if (im->out.f || im->in.mem.data) jpeg_destroy_compress(&(im->out.jinfo)); if (im->out.f) fclose(im->out.f); if (im->out.comment) free(im->out.comment); free(im); } static Epeg_Image * _epeg_open_header(Epeg_Image *im) { struct jpeg_marker_struct *m; struct jpeg_source_mgr *src_mgr = NULL; im->in.jinfo.err = jpeg_std_error(&(im->jerr.pub)); im->jerr.pub.error_exit = _epeg_fatal_error_handler; #ifdef NOWARNINGS im->jerr.pub.emit_message = _emit_message; im->jerr.pub.output_message = _output_message; im->jerr.pub.format_message = _format_message; #endif if (setjmp(im->jerr.setjmp_buffer)) { error: epeg_close(im); im = NULL; return NULL; } jpeg_create_decompress(&(im->in.jinfo)); jpeg_save_markers(&(im->in.jinfo), JPEG_APP0 + 7, 1024); jpeg_save_markers(&(im->in.jinfo), JPEG_COM, 65535); if (im->in.f != NULL) { jpeg_stdio_src(&(im->in.jinfo), im->in.f); } else { /* Setup RAM source manager. */ src_mgr = calloc(1, sizeof(struct jpeg_source_mgr)); if (!src_mgr) goto error; src_mgr->init_source = _jpeg_init_source; src_mgr->fill_input_buffer = _jpeg_fill_input_buffer; src_mgr->skip_input_data = _jpeg_skip_input_data; src_mgr->resync_to_restart = jpeg_resync_to_restart; src_mgr->term_source = _jpeg_term_source; src_mgr->bytes_in_buffer = im->in.mem.size; src_mgr->next_input_byte = (JOCTET *) im->in.mem.data; im->in.jinfo.src = (struct jpeg_source_mgr *) src_mgr; } jpeg_read_header(&(im->in.jinfo), TRUE); im->in.w = im->in.jinfo.image_width; im->in.h = im->in.jinfo.image_height; if (im->in.w < 1) goto error; if (im->in.h < 1) goto error; im->out.w = im->in.w; im->out.h = im->in.h; im->color_space = ((im->in.color_space = im->in.jinfo.out_color_space) == JCS_GRAYSCALE) ? EPEG_GRAY8 : EPEG_RGB8; if (im->in.color_space == JCS_CMYK) im->color_space = EPEG_CMYK; for (m = im->in.jinfo.marker_list; m; m = m->next) { if (m->marker == JPEG_COM) { if (im->in.comment) free(im->in.comment); im->in.comment = malloc(m->data_length + 1); if (im->in.comment) { memcpy(im->in.comment, m->data, m->data_length); im->in.comment[m->data_length] = 0; } } else if (m->marker == (JPEG_APP0 + 7)) { if ((m->data_length > 7) && (!strncmp((char *)m->data, "Thumb::", 7))) { char *p, *p2; p = malloc(m->data_length + 1); if (p) { memcpy(p, m->data, m->data_length); p[m->data_length] = 0; p2 = strchr(p, '\n'); if (p2) { p2[0] = 0; if (!strcmp(p, "Thumb::URI")) im->in.thumb_info.uri = strdup(p2 + 1); else if (!strcmp(p, "Thumb::MTime")) sscanf(p2 + 1, "%llu", &(im->in.thumb_info.mtime)); else if (!strcmp(p, "Thumb::Image::Width")) im->in.thumb_info.w = atoi(p2 + 1); else if (!strcmp(p, "Thumb::Image::Height")) im->in.thumb_info.h = atoi(p2 + 1); else if (!strcmp(p, "Thumb::Mimetype")) im->in.thumb_info.mime = strdup(p2 + 1); } free(p); } } } } return im; } /** retval 1 - malloc or other 2 - setjmp error */ static int _epeg_decode(Epeg_Image *im) { int scale, scalew, scaleh, y; JDIMENSION old_output_scanline = 1; if (im->pixels) return 1; if ((im->out.w < 1) || (im->out.h < 1)) return 1; scalew = im->in.w / im->out.w; scaleh = im->in.h / im->out.h; scale = scalew; if (scaleh < scalew) scale = scaleh; if (scale > 8) scale = 8; else if (scale < 1) scale = 1; im->in.jinfo.scale_num = 1; im->in.jinfo.scale_denom = scale; im->in.jinfo.do_fancy_upsampling = FALSE; im->in.jinfo.do_block_smoothing = FALSE; im->in.jinfo.dct_method = JDCT_IFAST; switch (im->color_space) { case EPEG_GRAY8: im->in.jinfo.out_color_space = JCS_GRAYSCALE; im->in.jinfo.output_components = 1; break; case EPEG_YUV8: im->in.jinfo.out_color_space = JCS_YCbCr; break; case EPEG_RGB8: case EPEG_BGR8: case EPEG_RGBA8: case EPEG_BGRA8: case EPEG_ARGB32: im->in.jinfo.out_color_space = JCS_RGB; break; case EPEG_CMYK: im->in.jinfo.out_color_space = JCS_CMYK; im->in.jinfo.output_components = 4; break; default: break; } im->out.jinfo.err = jpeg_std_error(&(im->jerr.pub)); im->jerr.pub.error_exit = _epeg_fatal_error_handler; #ifdef NOWARNINGS im->jerr.pub.emit_message = _emit_message; im->jerr.pub.output_message = _output_message; im->jerr.pub.format_message = _format_message; #endif if (setjmp(im->jerr.setjmp_buffer)) return 2; jpeg_calc_output_dimensions(&(im->in.jinfo)); im->pixels = malloc(im->in.jinfo.output_width * im->in.jinfo.output_height * im->in.jinfo.output_components); if (!im->pixels) return 1; im->lines = malloc(im->in.jinfo.output_height * sizeof(char *)); if (!im->lines) { free(im->pixels); im->pixels = NULL; return 1; } jpeg_start_decompress(&(im->in.jinfo)); for (y = 0; y < im->in.jinfo.output_height; y++) im->lines[y] = im->pixels + (y * im->in.jinfo.output_components * im->in.jinfo.output_width); while (im->in.jinfo.output_scanline < im->in.jinfo.output_height) { if (old_output_scanline == im->in.jinfo.output_scanline) { jpeg_abort_decompress(&(im->in.jinfo)); return 1; } old_output_scanline = im->in.jinfo.output_scanline; jpeg_read_scanlines(&(im->in.jinfo), &(im->lines[im->in.jinfo.output_scanline]), im->in.jinfo.rec_outbuf_height); } jpeg_finish_decompress(&(im->in.jinfo)); return 0; } static int _epeg_scale(Epeg_Image *im) { unsigned char *dst, *row, *src; int x, y, w, h, i; if ((im->in.w == im->out.w) && (im->in.h == im->out.h)) return 0; if (im->scaled) return 0; if ((im->out.w < 1) || (im->out.h < 1)) return 0; im->scaled = 1; w = im->out.w; h = im->out.h; for (y = 0; y < h; y++) { row = im->pixels + (((y * im->in.jinfo.output_height) / h) * im->in.jinfo.output_components * im->in.jinfo.output_width); dst = im->pixels + (y * im->in.jinfo.output_components * im->in.jinfo.output_width); for (x = 0; x < im->out.w; x++) { src = row + (((x * im->in.jinfo.output_width) / w) * im->in.jinfo.output_components); for (i = 0; i < im->in.jinfo.output_components; i++) dst[i] = src[i]; dst += im->in.jinfo.output_components; } } return 0; } static int _epeg_decode_for_trim(Epeg_Image *im) { int y; if (im->pixels) return 1; im->in.jinfo.scale_num = 1; im->in.jinfo.scale_denom = 1; im->in.jinfo.do_fancy_upsampling = FALSE; im->in.jinfo.do_block_smoothing = FALSE; im->in.jinfo.dct_method = JDCT_ISLOW; switch (im->color_space) { case EPEG_GRAY8: im->in.jinfo.out_color_space = JCS_GRAYSCALE; im->in.jinfo.output_components = 1; break; case EPEG_YUV8: im->in.jinfo.out_color_space = JCS_YCbCr; break; case EPEG_RGB8: case EPEG_BGR8: case EPEG_RGBA8: case EPEG_BGRA8: case EPEG_ARGB32: im->in.jinfo.out_color_space = JCS_RGB; break; case EPEG_CMYK: im->in.jinfo.out_color_space = JCS_CMYK; im->in.jinfo.output_components = 4; break; default: break; } im->out.jinfo.err = jpeg_std_error(&(im->jerr.pub)); im->jerr.pub.error_exit = _epeg_fatal_error_handler; #ifdef NOWARNINGS im->jerr.pub.emit_message = _emit_message; im->jerr.pub.output_message = _output_message; im->jerr.pub.format_message = _format_message; #endif if (setjmp(im->jerr.setjmp_buffer)) return 1; jpeg_calc_output_dimensions(&(im->in.jinfo)); im->pixels = malloc(im->in.jinfo.output_width * im->in.jinfo.output_height * im->in.jinfo.output_components); if (!im->pixels) return 1; im->lines = malloc(im->in.jinfo.output_height * sizeof(char *)); if (!im->lines) { free(im->pixels); im->pixels = NULL; return 1; } jpeg_start_decompress(&(im->in.jinfo)); for (y = 0; y < im->in.jinfo.output_height; y++) im->lines[y] = im->pixels + (y * im->in.jinfo.output_components * im->in.jinfo.output_width); while (im->in.jinfo.output_scanline < im->in.jinfo.output_height) jpeg_read_scanlines(&(im->in.jinfo), &(im->lines[im->in.jinfo.output_scanline]), im->in.jinfo.rec_outbuf_height); jpeg_finish_decompress(&(im->in.jinfo)); return 0; } static int _epeg_trim(Epeg_Image *im) { int y, a, b, w, h; if ((im->in.w == im->out.w) && (im->in.h == im->out.h)) return 1; if (im->scaled) return 1; im->scaled = 1; w = im->out.w; h = im->out.h; a = im->out.x; b = im->out.y; for (y = 0; y < h; y++) im->lines[y] = im->pixels + ((y+b) * im->in.jinfo.output_components * im->in.jinfo.output_width) + (a * im->in.jinfo.output_components); return 0; } struct epeg_destination_mgr { struct jpeg_destination_mgr dst_mgr; Epeg_Image *im; unsigned char *buf; }; static int _epeg_encode(Epeg_Image *im) { struct epeg_destination_mgr *dst_mgr = NULL; int ok = 0; if ((im->out.w < 1) || (im->out.h < 1)) return 1; if (im->out.f) return 1; if (im->out.file) { im->out.f = fopen(im->out.file, "wb"); if (!im->out.f) { im->error = 1; return 1; } } else im->out.f = NULL; im->out.jinfo.err = jpeg_std_error(&(im->jerr.pub)); im->jerr.pub.error_exit = _epeg_fatal_error_handler; #ifdef NOWARNINGS im->jerr.pub.emit_message = _emit_message; im->jerr.pub.output_message = _output_message; im->jerr.pub.format_message = _format_message; #endif if (setjmp(im->jerr.setjmp_buffer)) { ok = 1; im->error = 1; goto done; } jpeg_create_compress(&(im->out.jinfo)); if (im->out.f) jpeg_stdio_dest(&(im->out.jinfo), im->out.f); else { *(im->out.mem.data) = NULL; *(im->out.mem.size) = 0; /* Setup RAM destination manager */ dst_mgr = calloc(1, sizeof(struct epeg_destination_mgr)); if (!dst_mgr) return 1; dst_mgr->dst_mgr.init_destination = _jpeg_init_destination; dst_mgr->dst_mgr.empty_output_buffer = _jpeg_empty_output_buffer; dst_mgr->dst_mgr.term_destination = _jpeg_term_destination; dst_mgr->im = im; dst_mgr->buf = malloc(65536); if (!dst_mgr->buf) { ok = 1; im->error = 1; goto done; } im->out.jinfo.dest = (struct jpeg_destination_mgr *)dst_mgr; } im->out.jinfo.image_width = im->out.w; im->out.jinfo.image_height = im->out.h; im->out.jinfo.input_components = im->in.jinfo.output_components; im->out.jinfo.in_color_space = im->in.jinfo.out_color_space; im->out.jinfo.dct_method = im->in.jinfo.dct_method; jpeg_set_defaults(&(im->out.jinfo)); jpeg_set_quality(&(im->out.jinfo), im->out.quality, TRUE); if (im->out.quality >= 90) { im->out.jinfo.comp_info[0].h_samp_factor = 1; im->out.jinfo.comp_info[0].v_samp_factor = 1; im->out.jinfo.comp_info[1].h_samp_factor = 1; im->out.jinfo.comp_info[1].v_samp_factor = 1; im->out.jinfo.comp_info[2].h_samp_factor = 1; im->out.jinfo.comp_info[2].v_samp_factor = 1; } jpeg_start_compress(&(im->out.jinfo), TRUE); if (im->out.comment) jpeg_write_marker(&(im->out.jinfo), JPEG_COM, im->out.comment, strlen(im->out.comment)); if (im->out.thumbnail_info) { char buf[8192]; if (im->in.file) { snprintf(buf, sizeof(buf), "Thumb::URI\nfile://%s", im->in.file); jpeg_write_marker(&(im->out.jinfo), JPEG_APP0 + 7, buf, strlen(buf)); snprintf(buf, sizeof(buf), "Thumb::MTime\n%llu", (unsigned long long int)im->stat_info.st_mtime); } jpeg_write_marker(&(im->out.jinfo), JPEG_APP0 + 7, buf, strlen(buf)); snprintf(buf, sizeof(buf), "Thumb::Image::Width\n%i", im->in.w); jpeg_write_marker(&(im->out.jinfo), JPEG_APP0 + 7, buf, strlen(buf)); snprintf(buf, sizeof(buf), "Thumb::Image::Height\n%i", im->in.h); jpeg_write_marker(&(im->out.jinfo), JPEG_APP0 + 7, buf, strlen(buf)); snprintf(buf, sizeof(buf), "Thumb::Mimetype\nimage/jpeg"); jpeg_write_marker(&(im->out.jinfo), JPEG_APP0 + 7, buf, strlen(buf)); } while (im->out.jinfo.next_scanline < im->out.h) jpeg_write_scanlines(&(im->out.jinfo), &(im->lines[im->out.jinfo.next_scanline]), 1); jpeg_finish_compress(&(im->out.jinfo)); done: if ((im->in.f) || (im->in.mem.data != NULL)) jpeg_destroy_decompress(&(im->in.jinfo)); if ((im->in.f) && (im->in.file)) fclose(im->in.f); if (dst_mgr) { if (dst_mgr->buf) free(dst_mgr->buf); free(dst_mgr); im->out.jinfo.dest = NULL; } jpeg_destroy_compress(&(im->out.jinfo)); if ((im->out.f) && (im->out.file)) fclose(im->out.f); im->in.f = NULL; im->out.f = NULL; return ok; } static void _epeg_fatal_error_handler(j_common_ptr cinfo) { emptr errmgr; errmgr = (emptr)cinfo->err; longjmp(errmgr->setjmp_buffer, 1); return; } /* Source manager methods */ METHODDEF(void) _jpeg_decompress_error_exit(j_common_ptr cinfo) { } METHODDEF(void) _jpeg_init_source(j_decompress_ptr cinfo) { } METHODDEF(boolean) _jpeg_fill_input_buffer(j_decompress_ptr cinfo) { WARNMS(cinfo, JWRN_JPEG_EOF); /* Insert a fake EOI marker */ cinfo->src->next_input_byte = fake_EOI; cinfo->src->bytes_in_buffer = sizeof(fake_EOI); return TRUE; } METHODDEF(void) _jpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { if (num_bytes > (long)(cinfo)->src->bytes_in_buffer) ERREXIT(cinfo, 0); (cinfo)->src->next_input_byte += num_bytes; (cinfo)->src->bytes_in_buffer -= num_bytes; } METHODDEF(void) _jpeg_term_source(j_decompress_ptr cinfo) { } /* destination manager methods */ METHODDEF(void) _jpeg_init_destination(j_compress_ptr cinfo) { struct epeg_destination_mgr *dst_mgr; dst_mgr = (struct epeg_destination_mgr *)cinfo->dest; dst_mgr->dst_mgr.free_in_buffer = 65536; dst_mgr->dst_mgr.next_output_byte = (JOCTET *)dst_mgr->buf; } METHODDEF(boolean) _jpeg_empty_output_buffer(j_compress_ptr cinfo) { struct epeg_destination_mgr *dst_mgr; unsigned char *p; int psize; dst_mgr = (struct epeg_destination_mgr *)cinfo->dest; psize = *(dst_mgr->im->out.mem.size); *(dst_mgr->im->out.mem.size) += 65536; p = realloc(*(dst_mgr->im->out.mem.data), *(dst_mgr->im->out.mem.size)); if (p) { memcpy(p + psize, dst_mgr->buf, 65536); dst_mgr->dst_mgr.free_in_buffer = 65536; dst_mgr->dst_mgr.next_output_byte = (JOCTET *)dst_mgr->buf; } else return FALSE; return TRUE; } METHODDEF(void) _jpeg_term_destination(j_compress_ptr cinfo) { struct epeg_destination_mgr *dst_mgr; unsigned char *p; int psize; dst_mgr = (struct epeg_destination_mgr *)cinfo->dest; psize = *(dst_mgr->im->out.mem.size); *(dst_mgr->im->out.mem.size) += 65536 - dst_mgr->dst_mgr.free_in_buffer; p = realloc(*(dst_mgr->im->out.mem.data), *(dst_mgr->im->out.mem.size)); if (p) { *(dst_mgr->im->out.mem.data) = p; memcpy(p + psize, dst_mgr->buf, 65536 - dst_mgr->dst_mgr.free_in_buffer); } } /* be noisy - not */ METHODDEF(void) _emit_message(j_common_ptr cinfo, int msg_level) { } METHODDEF(void) _output_message(j_common_ptr cinfo) { } METHODDEF(void) _format_message(j_common_ptr cinfo, char * buffer) { }