/* * Copyright (c) 2010-2019 Julien Nadeau Carriere * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Loader for JPEG images via libjpeg. */ #include #include #include #include #ifdef HAVE_JPEG #include #include #include struct ag_jpg_errmgr { struct jpeg_error_mgr errmgr; jmp_buf escape; }; struct ag_jpg_sourcemgr { struct jpeg_source_mgr pub; AG_DataSource *ds; Uint8 buffer[4096]; }; /* * Callbacks */ static void AG_JPG_InitSource(j_decompress_ptr cinfo) { /* no-op */ } static boolean AG_JPG_FillInputBuffer(j_decompress_ptr cinfo) { struct ag_jpg_sourcemgr *sm = (struct ag_jpg_sourcemgr *)cinfo->src; AG_Size rv; if (AG_ReadP(sm->ds, sm->buffer, sizeof(sm->buffer), &rv) == -1) { return (FALSE); } if (rv == 0) { /* Reached EOF */ sm->buffer[0] = 0xff; sm->buffer[1] = (Uint8)JPEG_EOI; rv = 2; } sm->pub.next_input_byte = sm->buffer; sm->pub.bytes_in_buffer = rv; return (TRUE); } static void AG_JPG_SkipInputData(j_decompress_ptr cinfo, long len) { struct ag_jpg_sourcemgr *sm = (struct ag_jpg_sourcemgr *)cinfo->src; if (len > 0) { while (len > (long)sm->pub.bytes_in_buffer) { len -= (long)sm->pub.bytes_in_buffer; sm->pub.fill_input_buffer(cinfo); } sm->pub.next_input_byte += (AG_Size)len; sm->pub.bytes_in_buffer -= (AG_Size)len; } } static void AG_JPG_TermSource(j_decompress_ptr cinfo) { /* no-op */ } static void AG_JPG_ErrorExit(j_common_ptr cinfo) { struct ag_jpg_errmgr *err = (struct ag_jpg_errmgr *)cinfo->err; longjmp(err->escape, 1); } static void AG_JPG_OutputMessage(j_common_ptr cinfo) { /* no-op */ } /* Load a surface from a JPEG image file. */ AG_Surface * AG_SurfaceFromJPEG(const char *_Nonnull path) { AG_DataSource *ds; AG_Surface *s; if ((ds = AG_OpenFile(path, "rb")) == NULL) { return (NULL); } if ((s = AG_ReadSurfaceFromJPEG(ds)) == NULL) { AG_SetError("%s: %s", path, AG_GetError()); AG_CloseFile(ds); return (NULL); } AG_CloseFile(ds); return (s); } /* Export a surface to a JPEG image file. */ int AG_SurfaceExportJPEG(const AG_Surface *_Nonnull S, const char *_Nonnull path, int quality, Uint8 flags) { const Uint BytesPerPixel = S->format.BytesPerPixel; struct jpeg_error_mgr jerrmgr; struct jpeg_compress_struct jcomp; JSAMPROW row[1]; Uint8 *jcopybuf; FILE *f; int x; if ((f = fopen(path, "wb")) == NULL) { AG_SetError("fdopen: %s", strerror(errno)); return (-1); } jcomp.err = jpeg_std_error(&jerrmgr); jpeg_create_compress(&jcomp); jcomp.image_width = S->w; jcomp.image_height = S->h; jcomp.input_components = 3; jcomp.in_color_space = JCS_RGB; jpeg_set_defaults(&jcomp); jpeg_set_quality(&jcomp, quality, TRUE); if (flags & AG_EXPORT_JPEG_JDCT_ISLOW) { jcomp.dct_method = JDCT_ISLOW; } if (flags & AG_EXPORT_JPEG_JDCT_IFAST) { jcomp.dct_method = JDCT_IFAST; } if (flags & AG_EXPORT_JPEG_JDCT_FLOAT) { jcomp.dct_method = JDCT_FLOAT; } jpeg_stdio_dest(&jcomp, f); if ((jcopybuf = TryMalloc(S->w * 3)) == NULL) { jpeg_destroy_compress(&jcomp); fclose(f); return (-1); } /* TODO block copiable and optimized cases */ jpeg_start_compress(&jcomp, TRUE); while (jcomp.next_scanline < jcomp.image_height) { Uint8 *p = S->pixels + jcomp.next_scanline*S->pitch; Uint8 *jp = jcopybuf; for (x = 0; x < S->w; x++) { AG_Color c; AG_GetColor(&c, AG_SurfaceGet_At(S,p), &S->format); *jp++ = c.r; *jp++ = c.g; *jp++ = c.b; p += BytesPerPixel; } row[0] = jcopybuf; jpeg_write_scanlines(&jcomp, row, 1); } jpeg_finish_compress(&jcomp); jpeg_destroy_compress(&jcomp); fclose(f); Free(jcopybuf); return (0); } /* Load surface contents from a JPEG image file. */ AG_Surface * AG_ReadSurfaceFromJPEG(AG_DataSource *ds) { struct jpeg_decompress_struct cinfo; JSAMPROW rowptr[1]; AG_Surface *volatile S = NULL; AG_Offset start = AG_Tell(ds); struct ag_jpg_errmgr jerrmgr; struct ag_jpg_sourcemgr *sm; cinfo.err = jpeg_std_error(&jerrmgr.errmgr); jerrmgr.errmgr.error_exit = AG_JPG_ErrorExit; jerrmgr.errmgr.output_message = AG_JPG_OutputMessage; if (setjmp(jerrmgr.escape)) { jpeg_destroy_decompress(&cinfo); if (S != NULL) { AG_SurfaceFree(S); } AG_SetError("Error loading JPEG file"); goto fail; } jpeg_create_decompress(&cinfo); if (cinfo.src == NULL) { cinfo.src = (struct jpeg_source_mgr *) (*cinfo.mem->alloc_small)((j_common_ptr)&cinfo, JPOOL_PERMANENT, sizeof(struct ag_jpg_sourcemgr)); sm = (struct ag_jpg_sourcemgr *)cinfo.src; } sm = (struct ag_jpg_sourcemgr *)cinfo.src; sm->ds = ds; sm->pub.init_source = AG_JPG_InitSource; sm->pub.fill_input_buffer = AG_JPG_FillInputBuffer; sm->pub.skip_input_data = AG_JPG_SkipInputData; sm->pub.resync_to_restart = jpeg_resync_to_restart; sm->pub.term_source = AG_JPG_TermSource; sm->pub.bytes_in_buffer = 0; sm->pub.next_input_byte = NULL; jpeg_read_header(&cinfo, TRUE); if (cinfo.num_components == 4) { /* CMYK -> RGBA */ cinfo.out_color_space = JCS_CMYK; cinfo.quantize_colors = FALSE; jpeg_calc_output_dimensions(&cinfo); S = AG_SurfaceRGBA(cinfo.output_width, cinfo.output_height, 32, 0, #if AG_BYTEORDER == AG_BIG_ENDIAN 0x0000ff00, 0x00ff0000, 0xff000000, 0x000000ff #else 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 #endif ); Debug2(NULL, "JPEG image (%ux%u RGBA) at 0x%lx (->%p) via libjpeg-%d\n", S->w, S->w, (Ulong)AG_Tell(ds), S, JPEG_LIB_VERSION); } else { cinfo.out_color_space = JCS_RGB; /* RGB */ cinfo.quantize_colors = FALSE; jpeg_calc_output_dimensions(&cinfo); S = AG_SurfaceRGB(cinfo.output_width, cinfo.output_height, 24, 0, #if AG_BYTEORDER == AG_BIG_ENDIAN 0xff0000, 0x00ff00, 0x0000ff #else 0x0000ff, 0x00ff00, 0xff0000 #endif ); Debug2(NULL, "JPEG image (%ux%u RGB) at 0x%lx (->%p) via libjpeg-%d\n", S->w, S->w, (Ulong)AG_Tell(ds), S, JPEG_LIB_VERSION); } if (S == NULL) { jpeg_destroy_decompress(&cinfo); goto fail; } jpeg_start_decompress(&cinfo); while (cinfo.output_scanline < cinfo.output_height) { rowptr[0] = (JSAMPROW)S->pixels + cinfo.output_scanline * S->pitch; jpeg_read_scanlines(&cinfo, rowptr, (JDIMENSION) 1); } jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); return (S); fail: AG_Seek(ds, start, AG_SEEK_SET); return (NULL); } #else /* !HAVE_JPEG */ AG_Surface * AG_SurfaceFromJPEG(const char *path) { AG_SetError(_("Agar not compiled with JPEG support")); return (NULL); } int AG_SurfaceExportJPEG(const AG_Surface *su, const char *path, int quality, Uint8 flags) { AG_SetError(_("Agar not compiled with JPEG support")); return (-1); } AG_Surface * AG_ReadSurfaceFromJPEG(AG_DataSource *ds) { AG_SetError(_("Agar not compiled with JPEG support")); return (NULL); } #endif /* HAVE_JPEG */