diff --git a/LICENSE b/LICENSE index 3811118..3ed28a2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2017, chausner +Copyright (c) 2013, Christoph Hausner All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md new file mode 100644 index 0000000..c0f9807 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# gimp-jxr +GIMP plugin for reading and writing of JPEG XR image files. The plugin is also available on the [GIMP Plugin Registry](http://registry.gimp.org/node/25508). + +[![license](https://img.shields.io/github/license/chausner/gimp-jxr.svg)](https://github.com/chausner/gimp-jxr/blob/master/LICENSE) + +**NOTE:** Version 2.0 currently has a bug and writes non-standard files (that won't open in any other application) when the source image has an alpha channel. This will be fixed in a follow-up release. + +Features +-------- +Almost all pixel formats supported by JPEG XR can be loaded. Incompatible formats, however, will first be converted to a representation that GIMP understands (this means you'll loose HDR data, for example). All RGB pixel formats are converted to 24bpp RGB, all RGBA formats to 32bpp BGRA, and all grayscale formats to 8bpp Gray. Black-white images are imported as indexed images. + +Images are saved in one of the following pixel formats: +* 1bpp BlackWhite, if image mode is set to Indexed and the color map has exactly two entries black and white +* 8bpp Grayscale, for grayscale images +* 24bpp RGB, for color images without alpha channel +* 32bpp RGBA, for color images with alpha channel + +Save options include: +* Image quality +* Alpha channel quality +* Overlap¹ +* Chroma subsampling¹ +* Tiling¹ + +¹ see [jxrlib](http://jxrlib.codeplex.com) documentation for more information + +Installation +------------ +The plugin is designed to run with GIMP version 2.8.x. + +### Windows +Take the [pre-compiled binary](https://github.com/chausner/gimp-jxr/releases/latest) and put it into "%USERPROFILE%\\.gimp-2.8\plug-ins" (create the folder if it doesn't exist). Alternatively, run ```\bin\gimptool-2.0.exe --install-bin ```. + +### Ubuntu +1. Make sure GIMP 2.8.x is installed and you have all required development files: + ``` + sudo apt-get install libgimp2.0-dev libjxr0 libjxr-dev + ``` + +2. Grab the gimp-jxr source code via git: + ``` + git clone https://github.com/chausner/gimp-jxr.git + ``` + +3. Compile and install gimp-jxr: + ``` + cd gimp-jxr/src + export CFLAGS='-w -O -I/usr/include/jxrlib -D__ANSI__ -DDISABLE_PERF_MEASUREMENT load.c save.c utils.c' + export LIBS='-ljxrglue -ljpegxr' + gimptool-2.0 --install file-jxr.c + ``` \ No newline at end of file diff --git a/src/file-jxr.c b/src/file-jxr.c new file mode 100644 index 0000000..b47754c --- /dev/null +++ b/src/file-jxr.c @@ -0,0 +1,88 @@ +#include "file-jxr.h" + +static void query(); +static void run(const gchar* name, gint nparams, const GimpParam* param, gint* nreturn_vals, GimpParam** return_vals); +void load(gint nparams, const GimpParam* param, gint* nreturn_vals, GimpParam** return_vals); +void save(gint nparams, const GimpParam* param, gint* nreturn_vals, GimpParam** return_vals); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, + NULL, + (GimpQueryProc)query, + (GimpRunProc)run, +}; + +G_BEGIN_DECLS + +MAIN() + +static void query() +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "Interactive, non-interactive" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "Interactive, non-interactive" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE,"drawable", "Drawable to save" }, + { GIMP_PDB_STRING, "filename", "The name of the file to save the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" }, + { GIMP_PDB_INT32, "quality", "Quality of saved image (0 <= quality <= 100, 100 = lossless)" }, + { GIMP_PDB_INT32, "alpha-quality", "Quality of alpha channel (0 <= quality <= 100, 100 = lossless)" }, + { GIMP_PDB_INT32, "overlap", "Overlap level (0 = auto, 1 = none, 2 = one level, 3 = two level)" }, + { GIMP_PDB_INT32, "subsampling", "Chroma subsampling (0 = Y-only, 1 = 4:2:0, 2 = 4:2:2, 3 = 4:4:4)" }, + { GIMP_PDB_INT32, "tiling", "Tiling (0 = none, 1 = 256 x 256, 2 = 512 x 512, 3 = 1024 x 1024)" }, + }; + + gimp_install_procedure(LOAD_PROC, + N_("Loads JPEG XR images"), + "Loads JPEG XR image files.", + "Christoph Hausner", + "Christoph Hausner", + "2013", + N_("JPEG XR image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS(load_args), + G_N_ELEMENTS(load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime(LOAD_PROC, "image/vnd.ms-photo"); + gimp_register_magic_load_handler(LOAD_PROC, "jxr,wdp,hdp", "", "0,string,II\xBC"); + + gimp_install_procedure(SAVE_PROC, + N_("Saves JPEG XR images"), + "Saves JPEG XR image files.", + "Christoph Hausner", + "Christoph Hausner", + "2013", + N_("JPEG XR image"), + "RGB*, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS(save_args), 0, + save_args, 0); + + gimp_register_save_handler(SAVE_PROC, "jxr", ""); + gimp_register_file_handler_mime(SAVE_PROC, "image/vnd.ms-photo"); +} + +static void run(const gchar* name, gint nparams, const GimpParam* param, gint* nreturn_vals, GimpParam** return_vals) +{ + if (strcmp(name, LOAD_PROC) == 0) + load(nparams, param, nreturn_vals, return_vals); + else if (strcmp(name, SAVE_PROC) == 0) + save(nparams, param, nreturn_vals, return_vals); +} + +G_END_DECLS \ No newline at end of file diff --git a/src/file-jxr.h b/src/file-jxr.h new file mode 100644 index 0000000..aafb493 --- /dev/null +++ b/src/file-jxr.h @@ -0,0 +1,14 @@ +#ifndef FILE_JXR_H +#define FILE_JXR_H + +#include +#include + +#define LOAD_PROC "file-jxr-load" +#define SAVE_PROC "file-jxr-save" +#define PLUG_IN_BINARY "file-jxr" + +#define _(String) (String) +#define N_(String) (String) + +#endif diff --git a/src/load.c b/src/load.c new file mode 100644 index 0000000..cf180f8 --- /dev/null +++ b/src/load.c @@ -0,0 +1,289 @@ +#include "file-jxr.h" +#include +#include "utils.h" +#include + +static ERR jxrlib_load(const gchar* filename, gint* width, gint* height, gfloat* res_x, gfloat* res_y, PKPixelFormatGUID* pixel_format, gboolean* black_one, guchar** pixels, gchar** error_message); +static ERR get_target_pixel_format(const PKPixelFormatGUID* source, const PKPixelFormatGUID** target); +static void compact_stride(guchar* pixels, gint width, gint height, gint stride, gint bytes_per_pixel); + +void load(gint nparams, const GimpParam* param, gint* nreturn_vals, GimpParam** return_vals) +{ + GimpParam* ret_values; + + gchar* filename; + gchar* error_message; + ERR err; + + gint width; + gint height; + gfloat res_x; + gfloat res_y; + PKPixelFormatGUID pixel_format; + gboolean black_one; + guchar* pixels = NULL; + + GimpImageBaseType base_type; + GimpImageType image_type; + gint32 image_ID; + gint32 layer_ID; + GimpDrawable* drawable; + GimpPixelRgn pixel_rgn; + + /*clock_t time; + gchar* time_message;*/ + +/*#ifdef _DEBUG + while (TRUE) { } +#endif*/ + + filename = param[1].data.d_string; + + ret_values = g_new(GimpParam, 2); + + *nreturn_vals = 2; + *return_vals = ret_values; + + gimp_progress_init_printf(_("Loading '%s'"), gimp_filename_to_utf8(filename)); + + /*time = clock();*/ + + err = jxrlib_load(filename, &width, &height, &res_x, &res_y, &pixel_format, &black_one, &pixels, &error_message); + + if (Failed(err)) + { + if (pixels) + PKFreeAligned(&pixels); + + if (error_message == NULL) + { + switch (err) + { + case WMP_errFileIO: + error_message = _("Error opening file."); + break; + case WMP_errOutOfMemory: + error_message = _("Out of memory."); + break; + default: + error_message = _("An error occurred during image loading."); + break; + } + } + + ret_values[0].type = GIMP_PDB_STATUS; + ret_values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + ret_values[1].type = GIMP_PDB_STRING; + ret_values[1].data.d_string = error_message; + + gimp_progress_end(); + return; + } + + /*time = clock() - time; + + time_message = g_new(gchar, 128); + g_sprintf(time_message, _("Elapsed time: %f ms."), (double)(time) / CLOCKS_PER_SEC); + g_message(time_message);*/ + + if (IsEqualGUID(&pixel_format, &GUID_PKPixelFormat24bppRGB)) + { + base_type = GIMP_RGB; + image_type = GIMP_RGB_IMAGE; + } + else if (IsEqualGUID(&pixel_format, &GUID_PKPixelFormat32bppRGBA)) + { + base_type = GIMP_RGB; + image_type = GIMP_RGBA_IMAGE; + } + else if (IsEqualGUID(&pixel_format, &GUID_PKPixelFormat8bppGray)) + { + base_type = GIMP_GRAY; + image_type = GIMP_GRAY_IMAGE; + } + else if (IsEqualGUID(&pixel_format, &GUID_PKPixelFormatBlackWhite)) + { + base_type = GIMP_INDEXED; + image_type = GIMP_INDEXED_IMAGE; + } + + image_ID = gimp_image_new(width, height, base_type); + + gimp_image_set_filename(image_ID, filename); + gimp_image_set_resolution(image_ID, res_x, res_y); + + if (IsEqualGUID(&pixel_format, &GUID_PKPixelFormatBlackWhite)) + { + guchar colormap[] = { 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF }; + + if (black_one) + { + colormap[0] = colormap[1] = colormap[2] = 0xFF; + colormap[3] = colormap[4] = colormap[5] = 0x00; + } + + gimp_image_set_colormap(image_ID, colormap, 2); + } + + layer_ID = gimp_layer_new(image_ID, "Background", width, height, image_type, 100.0, GIMP_NORMAL_MODE); + drawable = gimp_drawable_get(layer_ID); + + gimp_pixel_rgn_init(&pixel_rgn, drawable, 0, 0, width, height, TRUE, FALSE); + gimp_pixel_rgn_set_rect(&pixel_rgn, pixels, 0, 0, width, height); + + gimp_drawable_update(layer_ID, 0, 0, width, height); + gimp_image_add_layer(image_ID, layer_ID, 0); + gimp_drawable_detach(drawable); + + PKFreeAligned(&pixels); + + ret_values[0].type = GIMP_PDB_STATUS; + ret_values[0].data.d_status = GIMP_PDB_SUCCESS; + ret_values[1].type = GIMP_PDB_IMAGE; + ret_values[1].data.d_image = image_ID; + + gimp_progress_end(); +} + +static ERR jxrlib_load(const gchar* filename, gint* width, gint* height, gfloat* res_x, gfloat* res_y, PKPixelFormatGUID* pixel_format, gboolean* black_one, guchar** pixels, gchar** error_message) +{ + ERR err; + PKCodecFactory* codec_factory = NULL; + PKImageDecode* decoder = NULL; + PKPixelFormatGUID* target_format; + PKFormatConverter* converter = NULL; + guint stride; + PKRect rect; + + *pixels = NULL; + *error_message = NULL; + + Call(PKCreateCodecFactory(&codec_factory, WMP_SDK_VERSION)); + + Call(codec_factory->CreateDecoderFromFile(filename, &decoder)); + + Call(decoder->GetSize(decoder, width, height)); + Call(decoder->GetResolution(decoder, res_x, res_y)); + Call(decoder->GetPixelFormat(decoder, pixel_format)); + + *black_one = decoder->WMP.wmiSCP.bBlackWhite; + + Call(codec_factory->CreateFormatConverter(&converter)); + + err = get_target_pixel_format(pixel_format, &target_format); + + if (!Failed(err)) + err = converter->Initialize(converter, decoder, NULL, *target_format); + + if (Failed(err)) + { + gchar* mnemonic = get_pixel_format_mnemonic(pixel_format); + + *error_message = g_new(gchar, 128); + + if (mnemonic != NULL) + g_sprintf(*error_message, _("Image has an unsupported pixel format (%s)."), mnemonic); + else + g_sprintf(*error_message, _("Image has an unsupported pixel format (%08X-%04X-%04X-%02X%02X%02X%02X%02X%02X%02X%02X)."), + pixel_format->Data1, pixel_format->Data2, pixel_format->Data3, pixel_format->Data4[0], + pixel_format->Data4[1], pixel_format->Data4[2], pixel_format->Data4[3], pixel_format->Data4[4], + pixel_format->Data4[5], pixel_format->Data4[6], pixel_format->Data4[7]); + + goto Cleanup; + } + + if (get_bits_per_pixel(target_format) < get_bits_per_pixel(pixel_format)) + { + g_message(_("Warning:\n" + "The image you are loading has a pixel format that is not directly supported by GIMP. " + "In order to load this image it needs to be converted to a lower bit depth first. " + "Information will be lost because of this conversion.")); + } + + decoder->WMP.wmiSCP.uAlphaMode = + IsEqualGUID(target_format, &GUID_PKPixelFormat32bppRGBA) ? 2 : 0; + + stride = (*width * max(get_bits_per_pixel(pixel_format), get_bits_per_pixel(target_format)) + 7) / 8; + + Call(PKAllocAligned(pixels, stride * *height, 128)); + + rect.X = 0; + rect.Y = 0; + rect.Width = *width; + rect.Height = *height; + + Call(converter->Copy(converter, &rect, *pixels, stride)); + + if (!IsEqualGUID(target_format, &GUID_PKPixelFormatBlackWhite) && + get_bits_per_pixel(pixel_format) > get_bits_per_pixel(target_format)) + { + guint bytes_per_pixel = get_bits_per_pixel(target_format) / 8; + compact_stride(*pixels, *width, *height, stride, bytes_per_pixel); + } + + if (IsEqualGUID(target_format, &GUID_PKPixelFormatBlackWhite)) + { + guchar* conv_pixels; + + Call(convert_bw_indexed(*pixels, *width, *height, &conv_pixels)); // should pass stride here + // if there were formats converted to BlackWhite + + Call(PKFreeAligned(pixels)); + *pixels = conv_pixels; + } + + *pixel_format = *target_format; + +Cleanup: + if (Failed(err) && *pixels) + PKFreeAligned(pixels); + + if (converter) + converter->Release(&converter); + + if (decoder) + decoder->Release(&decoder); + + if (codec_factory) + codec_factory->Release(&codec_factory); + + return err; +} + +static ERR get_target_pixel_format(const PKPixelFormatGUID* source, const PKPixelFormatGUID** target) +{ + ERR err; + PKPixelInfo pixel_info; + + pixel_info.pGUIDPixFmt = source; + + Call(PixelFormatLookup(&pixel_info, LOOKUP_FORWARD)); + + if (pixel_info.grBit & PK_pixfmtHasAlpha) + *target = &GUID_PKPixelFormat32bppRGBA; + else if (pixel_info.cfColorFormat == Y_ONLY) + if (pixel_info.cbitUnit == 1) + *target = &GUID_PKPixelFormatBlackWhite; + else + *target = &GUID_PKPixelFormat8bppGray; + else + *target = &GUID_PKPixelFormat24bppRGB; + +Cleanup: + return err; +} + +static void compact_stride(guchar* pixels, gint width, gint height, gint stride, gint bytes_per_pixel) +{ + gint y; + guchar* src; + guchar* dst; + gint new_stride = width * bytes_per_pixel; + + for (y = 1; y < height; y++) + { + src = pixels + y * stride; + dst = pixels + y * new_stride; + g_memmove(dst, src, new_stride); + } +} diff --git a/src/save.c b/src/save.c new file mode 100644 index 0000000..27acba8 --- /dev/null +++ b/src/save.c @@ -0,0 +1,613 @@ +#include "file-jxr.h" +#include +#include "utils.h" + +#include + +typedef enum +{ + OVERLAP_AUTO, + OVERLAP_NONE, + OVERLAP_ONE, + OVERLAP_TWO +} OverlapSetting; + +typedef enum +{ + SUBSAMPLING_YONLY, + SUBSAMPLING_420, + SUBSAMPLING_422, + SUBSAMPLING_444 +} SubsamplingSetting; + +typedef enum +{ + TILING_NONE, + TILING_256, + TILING_512, + TILING_1024 +} TilingSetting; + +typedef struct +{ + gint image_quality; + gint alpha_quality; + OverlapSetting overlap; + SubsamplingSetting subsampling; + TilingSetting tiling; +} SaveOptions; + +typedef struct +{ + GtkWidget* dialog; + GtkWidget* vbox; + GtkWidget* quality_table; + GtkObject* quality_entry; + GtkObject* alpha_quality_entry; + GtkWidget* advanced_expander; + GtkWidget* advanced_vbox; + GtkWidget* advanced_frame; + GtkWidget* advanced_table; + GtkWidget* overlap_label; + GtkWidget* overlap_combo_box; + GtkWidget* subsampling_label; + GtkWidget* subsampling_combo_box; + GtkWidget* tiling_label; + GtkWidget* tiling_combo_box; + GtkWidget* lossless_label; + GtkWidget* defaults_table; + GtkWidget* defaults_button; +} SaveGui; + +static const SaveOptions DEFAULT_SAVE_OPTIONS = { 90, 100, OVERLAP_AUTO, SUBSAMPLING_444, TILING_NONE }; + +static ERR jxrlib_save(const gchar *filename, guint width, guint height, guint stride, gfloat res_x, gfloat res_y, PKPixelFormatGUID pixel_format, gboolean black_one, const guchar *pixels, const SaveOptions* save_options); +static void applySaveOptions(const SaveOptions* save_options, guint width, guint height, PKPixelFormatGUID pixel_format, gboolean black_one, CWMIStrCodecParam* wmiSCP, CWMIStrCodecParam* wmiSCP_Alpha); +static gboolean show_options(SaveOptions* save_options, gboolean alpha_enabled, gboolean subsampling_enabled); +static void load_save_gui_defaults(const SaveGui* save_gui); +static void open_help(const gchar* help_id, gpointer help_data); + +void save(gint nparams, const GimpParam* param, gint* nreturn_vals, GimpParam** return_vals) +{ + GimpParam* ret_values; + GimpExportCapabilities capabilities; + GimpExportReturn export_return; + + SaveOptions save_options = DEFAULT_SAVE_OPTIONS; + + gint width; + gint height; + gchar* filename; + gdouble res_x; + gdouble res_y; + + GimpRunMode run_mode; + gint32 image_ID; + gint32 drawable_ID; + GimpImageType image_type; + + gboolean black_one; + + GimpPixelRgn pixel_rgn; + GimpDrawable* drawable; + guchar* pixels; + guint stride; + PKPixelFormatGUID pixel_format; + + ERR err; + + gboolean alpha_enabled; + gboolean subsampling_enabled; + + run_mode = (GimpRunMode)param[0].data.d_int32; + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + filename = param[3].data.d_string; + + ret_values = g_new(GimpParam, 2); + + *nreturn_vals = 2; + *return_vals = ret_values; + ret_values[0].type = GIMP_PDB_STATUS; + ret_values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + gimp_ui_init(PLUG_IN_BINARY, FALSE); + + capabilities = GIMP_EXPORT_CAN_HANDLE_RGB | GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED | GIMP_EXPORT_CAN_HANDLE_ALPHA; + +Export: + export_return = gimp_export_image(&image_ID, &drawable_ID, "JPEG XR", capabilities); + + if (export_return == GIMP_EXPORT_CANCEL) + { + ret_values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + + image_type = gimp_drawable_type(drawable_ID); + + switch (image_type) + { + case GIMP_RGB_IMAGE: + pixel_format = GUID_PKPixelFormat24bppRGB; + break; + case GIMP_RGBA_IMAGE: + pixel_format = GUID_PKPixelFormat32bppRGBA; + break; + case GIMP_GRAY_IMAGE: + pixel_format = GUID_PKPixelFormat8bppGray; + break; + case GIMP_GRAYA_IMAGE: + ret_values[1].type = GIMP_PDB_STRING; + ret_values[1].data.d_string = _("Grayscale images with an alpha channel are not supported."); + return; + case GIMP_INDEXED_IMAGE: + if (has_blackwhite_colormap(image_ID, &black_one)) + { + pixel_format = GUID_PKPixelFormatBlackWhite; + } + else + { + ret_values[1].type = GIMP_PDB_STRING; + ret_values[1].data.d_string = _("Indexed images are not supported except for black-white colormaps."); + return; + /*gimp_image_delete(image_ID); + capabilities &= GIMP_EXPORT_CAN_HANDLE_RGB | GIMP_EXPORT_CAN_HANDLE_GRAY | GIMP_EXPORT_CAN_HANDLE_ALPHA; + goto Export;*/ + } + break; + case GIMP_INDEXEDA_IMAGE: + ret_values[1].type = GIMP_PDB_STRING; + ret_values[1].data.d_string = _("Indexed images with an alpha channel are not supported."); + return; + default: + ret_values[1].type = GIMP_PDB_STRING; + ret_values[1].data.d_string = _("Image has an unsupported pixel format."); + return; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data(SAVE_PROC, &save_options); + alpha_enabled = IsEqualGUID(&pixel_format, &GUID_PKPixelFormat32bppRGBA); + subsampling_enabled = IsEqualGUID(&pixel_format, &GUID_PKPixelFormat24bppRGB) || + IsEqualGUID(&pixel_format, &GUID_PKPixelFormat32bppRGBA); + if (show_options(&save_options, alpha_enabled, subsampling_enabled)) + { + gimp_set_data(SAVE_PROC, &save_options, sizeof(SaveOptions)); + } + else + { + ret_values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams == 10) + { + save_options.image_quality = param[5].data.d_int32; + save_options.alpha_quality = param[6].data.d_int32; + save_options.overlap = param[7].data.d_int32; + save_options.subsampling = param[8].data.d_int32; + save_options.tiling = param[9].data.d_int32; + + if (save_options.image_quality < 0 || save_options.image_quality > 100 || + save_options.alpha_quality < 0 || save_options.alpha_quality > 100 || + save_options.overlap < 0 || save_options.overlap > 3 || + save_options.subsampling < 0 || save_options.subsampling > 3 || + save_options.tiling < 0 || save_options.tiling > 3) + { + ret_values[0].data.d_status = GIMP_PDB_CALLING_ERROR; + return; + } + } + else + { + ret_values[0].data.d_status = GIMP_PDB_CALLING_ERROR; + return; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data(SAVE_PROC, &save_options); + break; + } + + gimp_progress_init_printf(_("Saving '%s'"), gimp_filename_to_utf8(filename)); + + drawable = gimp_drawable_get(drawable_ID); + + width = drawable->width; + height = drawable->height; + + if (!gimp_image_get_resolution(image_ID, &res_x, &res_y)) + { + res_x = 72.0; + res_y = 72.0; + } + + stride = width * drawable->bpp; + + err = PKAllocAligned(&pixels, stride * height, 128); + + if (Failed(err)) + { + ret_values[1].type = GIMP_PDB_STRING; + ret_values[1].data.d_string = _("Out of memory."); + return; + } + + gimp_pixel_rgn_init(&pixel_rgn, drawable, 0, 0, width, height, FALSE, FALSE); + gimp_pixel_rgn_get_rect(&pixel_rgn, pixels, 0, 0, width, height); + + gimp_drawable_detach(drawable); + + if (export_return == GIMP_EXPORT_EXPORT) + gimp_image_delete(image_ID); + + if (IsEqualGUID(&pixel_format, &GUID_PKPixelFormatBlackWhite)) + { + convert_indexed_bw(pixels, width, height); + stride = (width + 7) / 8; + } + + err = jxrlib_save(filename, width, height, stride, res_x, res_y, pixel_format, black_one, pixels, &save_options); + + PKFreeAligned(&pixels); + + if (!Failed(err)) + { + *nreturn_vals = 1; + ret_values[0].data.d_status = GIMP_PDB_SUCCESS; + } + else + { + ret_values[1].type = GIMP_PDB_STRING; + ret_values[1].data.d_string = _("An error occurred."); + } + + gimp_progress_end(); +} + +static ERR jxrlib_save(const gchar *filename, guint width, guint height, guint stride, gfloat res_x, gfloat res_y, PKPixelFormatGUID pixel_format, gboolean black_one, const guchar *pixels, const SaveOptions* save_options) +{ + ERR err; + PKFactory* factory = NULL; + struct WMPStream* stream; + PKCodecFactory* codec_factory = NULL; + PKImageEncode* encoder = NULL; + CWMIStrCodecParam wmiSCP; + + Call(PKCreateFactory(&factory, PK_SDK_VERSION)); + Call(PKCreateCodecFactory(&codec_factory, WMP_SDK_VERSION)); + + Call(factory->CreateStreamFromFilename(&stream, filename, "wb")); + + Call(codec_factory->CreateCodec(&IID_PKImageWmpEncode, (void**)&encoder)); + + applySaveOptions(save_options, width, height, pixel_format, black_one, &wmiSCP, NULL); + + Call(encoder->Initialize(encoder, stream, &wmiSCP, sizeof(wmiSCP))); + + applySaveOptions(save_options, width, height, pixel_format, black_one, &wmiSCP, &encoder->WMP.wmiSCP_Alpha); + + Call(encoder->SetPixelFormat(encoder, pixel_format)); + Call(encoder->SetSize(encoder, width, height)); + Call(encoder->SetResolution(encoder, res_x, res_y)); + + Call(encoder->WritePixels(encoder, height, pixels, stride)); + +Cleanup: + if (encoder) + encoder->Release(&encoder); + + if (codec_factory) + codec_factory->Release(&codec_factory); + + if (factory) + factory->Release(&factory); + + return err; +} + +static int qp_table[12][6] = { + { 67, 79, 86, 72, 90, 98 }, + { 59, 74, 80, 64, 83, 89 }, + { 53, 68, 75, 57, 76, 83 }, + { 49, 64, 71, 53, 70, 77 }, + { 45, 60, 67, 48, 67, 74 }, + { 40, 56, 62, 42, 59, 66 }, + { 33, 49, 55, 35, 51, 58 }, + { 27, 44, 49, 28, 45, 50 }, + { 20, 36, 42, 20, 38, 44 }, + { 13, 27, 34, 13, 28, 34 }, + { 7, 17, 21, 8, 17, 21 }, + { 2, 5, 6, 2, 5, 6 } +}; + +static void applySaveOptions(const SaveOptions* save_options, guint width, guint height, PKPixelFormatGUID pixel_format, gboolean black_one, CWMIStrCodecParam* wmiSCP, CWMIStrCodecParam* wmiSCP_Alpha) +{ + gfloat iq_float; + + memset(wmiSCP, 0, sizeof(*wmiSCP)); + + wmiSCP->bVerbose = FALSE; + wmiSCP->bdBitDepth = BD_LONG; + wmiSCP->bfBitstreamFormat = FREQUENCY; + wmiSCP->bProgressiveMode = TRUE; + wmiSCP->sbSubband = SB_ALL; + wmiSCP->uAlphaMode = IsEqualGUID(&pixel_format, &GUID_PKPixelFormat32bppRGBA) ? 2 : 0; + wmiSCP->bBlackWhite = black_one; + + if (IsEqualGUID(&pixel_format, &GUID_PKPixelFormatBlackWhite) || + IsEqualGUID(&pixel_format, &GUID_PKPixelFormat8bppGray)) + wmiSCP->cfColorFormat = Y_ONLY; + else + wmiSCP->cfColorFormat = (COLORFORMAT)save_options->subsampling; + + iq_float = save_options->image_quality / 100.0f; + + if (iq_float == 1.0f) + wmiSCP->olOverlap = OL_NONE; + else if (save_options->overlap == OVERLAP_AUTO) + wmiSCP->olOverlap = iq_float > 0.4f ? OL_ONE : OL_TWO; + else + wmiSCP->olOverlap = save_options->overlap - 1; + + if (iq_float == 1.0f) + wmiSCP->uiDefaultQPIndex = 1; + else + { + if (IsEqualGUID(&pixel_format, &GUID_PKPixelFormatBlackWhite)) + wmiSCP->uiDefaultQPIndex = (U8)(8 - 5.0f * iq_float + 0.5f); + else + { + gfloat iq; + gint qi; + gint *qp_row; + float qf; + + if (iq_float > 0.8f) + iq = 0.8f + (iq_float - 0.8f) * 1.5f; + else + iq = iq_float; + + qi = (int)(10.0f * iq); + qf = 10.0f * iq - (float)qi; + + qp_row = qp_table[qi]; + + wmiSCP->uiDefaultQPIndex = (U8)(0.5f + qp_row[0] * (1.0f - qf) + (qp_row + 6)[0] * qf); + wmiSCP->uiDefaultQPIndexU = (U8)(0.5f + qp_row[1] * (1.0f - qf) + (qp_row + 6)[1] * qf); + wmiSCP->uiDefaultQPIndexV = (U8)(0.5f + qp_row[2] * (1.0f - qf) + (qp_row + 6)[2] * qf); + wmiSCP->uiDefaultQPIndexYHP = (U8)(0.5f + qp_row[3] * (1.0f - qf) + (qp_row + 6)[3] * qf); + wmiSCP->uiDefaultQPIndexUHP = (U8)(0.5f + qp_row[4] * (1.0f - qf) + (qp_row + 6)[4] * qf); + wmiSCP->uiDefaultQPIndexVHP = (U8)(0.5f + qp_row[5] * (1.0f - qf) + (qp_row + 6)[5] * qf); + } + } + + if (IsEqualGUID(&pixel_format, &GUID_PKPixelFormat32bppRGBA) && wmiSCP_Alpha != NULL) + { + gfloat aq_float = save_options->alpha_quality / 100.0f; + + if (aq_float == 1.0f) + wmiSCP_Alpha->uiDefaultQPIndex = 1; + else + { + gfloat aq; + int qi; + float qf; + + if (aq_float > 0.8f) + aq = 0.8f + (aq_float - 0.8f) * 1.5f; + else + aq = aq_float; + + qi = (int)(10.0f * aq); + qf = 10.0f * aq - (float)qi; + wmiSCP_Alpha->uiDefaultQPIndex = (U8)(0.5f + qp_table[qi][0] * (1.0f - qf) + qp_table[qi + 1][0] * qf); + } + + wmiSCP->uiDefaultQPIndexAlpha = wmiSCP_Alpha->uiDefaultQPIndex; + } + + if (save_options->tiling == TILING_NONE) + wmiSCP->cNumOfSliceMinus1H = wmiSCP->cNumOfSliceMinus1V = 0; + else + { + gint tile_size = 256 << (save_options->tiling - 1); + + gint i = 0; + gint p = 0; + + for (i = 0; i < MAX_TILES; i++) + { + wmiSCP->uiTileY[i] = tile_size / 16; + p += tile_size; + if (p >= height) + break; + } + + wmiSCP->cNumOfSliceMinus1H = i; + + p = 0; + + for (i = 0; i < MAX_TILES; i++) + { + wmiSCP->uiTileX[i] = tile_size / 16; + p += tile_size; + if (p >= width) + break; + } + + wmiSCP->cNumOfSliceMinus1V = i; + } +} + +static gboolean show_options(SaveOptions* save_options, gboolean alpha_enabled, gboolean subsampling_enabled) +{ + SaveGui save_gui; + gboolean dialog_result; + gchar* text; + + save_gui.dialog = gimp_export_dialog_new(_("JPEG XR"), PLUG_IN_BINARY, NULL); + + g_signal_connect(save_gui.dialog, "destroy", G_CALLBACK(gtk_main_quit), NULL); + + gtk_window_set_resizable(GTK_WINDOW(save_gui.dialog), FALSE); + + save_gui.vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width(GTK_CONTAINER(save_gui.vbox), 12); + gtk_box_pack_start(GTK_BOX(gimp_export_dialog_get_content_area(save_gui.dialog)), save_gui.vbox, TRUE, TRUE, 0); + gtk_widget_show(save_gui.vbox); + + save_gui.quality_table = gtk_table_new(2, 3, FALSE); + gtk_table_set_col_spacings(GTK_TABLE(save_gui.quality_table), 6); + gtk_table_set_row_spacings(GTK_TABLE(save_gui.quality_table), 6); + gtk_box_pack_start(GTK_BOX(save_gui.vbox), save_gui.quality_table, FALSE, FALSE, 0); + gtk_widget_show(save_gui.quality_table); + + save_gui.quality_entry = gimp_scale_entry_new(GTK_TABLE(save_gui.quality_table), 0, 0, _("_Quality:"), 125, 0, save_options->image_quality, + 0.0, 100.0, 1.0, 10.0, 0, TRUE, 0.0, 0.0, + _("Image quality parameter. The higher the value the better the quality. Choose 100 to compress the image losslessly."), + "file-jxr-save-quality"); + + save_gui.alpha_quality_entry = gimp_scale_entry_new(GTK_TABLE(save_gui.quality_table), 0, 1, _("A_lpha quality:"), 125, 0, save_options->alpha_quality, + 0.0, 100.0, 1.0, 10.0, 0, TRUE, 0.0, 0.0, + _("Alpha channel quality parameter. The higher the value the better the quality. Choose 100 to compress the alpha channel losslessly."), + "file-jxr-save-quality"); + + gimp_scale_entry_set_sensitive(save_gui.alpha_quality_entry, alpha_enabled); + + save_gui.lossless_label = gtk_label_new(_("A value of 100 guarantees lossless compression.")); + gtk_misc_set_alignment(GTK_MISC(save_gui.lossless_label), 0.0, 0.5); + gtk_box_pack_start(GTK_BOX(save_gui.vbox), save_gui.lossless_label, FALSE, FALSE, 0); + gtk_widget_show(save_gui.lossless_label); + + text = g_strdup_printf("%s", _("_Advanced Options")); + save_gui.advanced_expander = gtk_expander_new_with_mnemonic(text); + gtk_expander_set_use_markup(GTK_EXPANDER(save_gui.advanced_expander), TRUE); + g_free(text); + gtk_box_pack_start(GTK_BOX(save_gui.vbox), save_gui.advanced_expander, FALSE, FALSE, 0); + gtk_widget_show(save_gui.advanced_expander); + + save_gui.advanced_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); + gtk_container_add(GTK_CONTAINER(save_gui.advanced_expander), save_gui.advanced_vbox); + gtk_widget_show(save_gui.advanced_vbox); + + save_gui.advanced_frame = gimp_frame_new(""); + gtk_box_pack_start(GTK_BOX(save_gui.advanced_vbox), save_gui.advanced_frame, FALSE, FALSE, 0); + gtk_widget_show(save_gui.advanced_frame); + + save_gui.advanced_table = gtk_table_new(3, 2, FALSE); + gtk_table_set_col_spacings(GTK_TABLE(save_gui.advanced_table), 6); + gtk_table_set_row_spacings(GTK_TABLE(save_gui.advanced_table), 6); + gtk_container_add(GTK_CONTAINER(save_gui.advanced_frame), save_gui.advanced_table); + gtk_widget_show(save_gui.advanced_table); + + save_gui.overlap_label = gtk_label_new_with_mnemonic(_("_Overlap:")); + gtk_misc_set_alignment(GTK_MISC(save_gui.overlap_label), 0.0, 0.5); + gtk_table_attach(GTK_TABLE(save_gui.advanced_table), save_gui.overlap_label, 0, 1, 0, 1, GTK_FILL, (GtkAttachOptions)0, 0, 0); + gtk_widget_show(save_gui.overlap_label); + + save_gui.overlap_combo_box = gtk_combo_box_new_text(); + gtk_combo_box_append_text(GTK_COMBO_BOX(save_gui.overlap_combo_box), _("Auto")); + gtk_combo_box_append_text(GTK_COMBO_BOX(save_gui.overlap_combo_box), _("None")); + gtk_combo_box_append_text(GTK_COMBO_BOX(save_gui.overlap_combo_box), _("One level")); + gtk_combo_box_append_text(GTK_COMBO_BOX(save_gui.overlap_combo_box), _("Two level")); + gtk_combo_box_set_active(GTK_COMBO_BOX(save_gui.overlap_combo_box), save_options->overlap); + gtk_widget_set_tooltip_text(save_gui.overlap_combo_box, _("Higher levels reduce block artifacts but may introduce blurring and increase decoding time.")); + gtk_label_set_mnemonic_widget(GTK_LABEL(save_gui.overlap_label), save_gui.overlap_combo_box); + gtk_table_attach(GTK_TABLE(save_gui.advanced_table), save_gui.overlap_combo_box, 1, 2, 0, 1, GTK_FILL, (GtkAttachOptions)0, 0, 0); + gtk_widget_show(save_gui.overlap_combo_box); + + save_gui.subsampling_label = gtk_label_new_with_mnemonic(_("Chroma _subsampling:")); + gtk_misc_set_alignment(GTK_MISC(save_gui.subsampling_label), 0.0, 0.5); + gtk_widget_set_sensitive(save_gui.subsampling_label, subsampling_enabled); + gtk_table_attach(GTK_TABLE(save_gui.advanced_table), save_gui.subsampling_label, 0, 1, 1, 2, GTK_FILL, (GtkAttachOptions)0, 0, 0); + gtk_widget_show(save_gui.subsampling_label); + + save_gui.subsampling_combo_box = gtk_combo_box_new_text(); + gtk_combo_box_append_text(GTK_COMBO_BOX(save_gui.subsampling_combo_box), _("Y-only")); + gtk_combo_box_append_text(GTK_COMBO_BOX(save_gui.subsampling_combo_box), _("4:2:0")); + gtk_combo_box_append_text(GTK_COMBO_BOX(save_gui.subsampling_combo_box), _("4:2:2")); + gtk_combo_box_append_text(GTK_COMBO_BOX(save_gui.subsampling_combo_box), _("4:4:4")); + gtk_combo_box_set_active(GTK_COMBO_BOX(save_gui.subsampling_combo_box), save_options->subsampling); + gtk_widget_set_tooltip_text(save_gui.subsampling_combo_box, _("4:4:4 usually provides best size/quality tradeoff.")); + gtk_widget_set_sensitive(save_gui.subsampling_combo_box, subsampling_enabled); + gtk_label_set_mnemonic_widget(GTK_LABEL(save_gui.subsampling_label), save_gui.subsampling_combo_box); + gtk_table_attach(GTK_TABLE(save_gui.advanced_table), save_gui.subsampling_combo_box, 1, 2, 1, 2, GTK_FILL, (GtkAttachOptions)0, 0, 0); + gtk_widget_show(save_gui.subsampling_combo_box); + + save_gui.tiling_label = gtk_label_new_with_mnemonic(_("_Tiling:")); + gtk_misc_set_alignment(GTK_MISC(save_gui.tiling_label), 0.0, 0.5); + gtk_table_attach(GTK_TABLE(save_gui.advanced_table), save_gui.tiling_label, 0, 1, 2, 3, GTK_FILL, (GtkAttachOptions)0, 0, 0); + gtk_widget_show(save_gui.tiling_label); + + save_gui.tiling_combo_box = gtk_combo_box_new_text(); + gtk_combo_box_append_text(GTK_COMBO_BOX(save_gui.tiling_combo_box), _("None")); + gtk_combo_box_append_text(GTK_COMBO_BOX(save_gui.tiling_combo_box), _("256 x 256")); + gtk_combo_box_append_text(GTK_COMBO_BOX(save_gui.tiling_combo_box), _("512 x 512")); + gtk_combo_box_append_text(GTK_COMBO_BOX(save_gui.tiling_combo_box), _("1024 x 1024")); + gtk_combo_box_set_active(GTK_COMBO_BOX(save_gui.tiling_combo_box), save_options->tiling); + gtk_widget_set_tooltip_text(save_gui.tiling_combo_box, _("Tiling optimizes the image for region decoding and is otherwise not needed.")); + gtk_label_set_mnemonic_widget(GTK_LABEL(save_gui.tiling_label), save_gui.tiling_combo_box); + gtk_table_attach(GTK_TABLE(save_gui.advanced_table), save_gui.tiling_combo_box, 1, 2, 2, 3, GTK_FILL, (GtkAttachOptions)0, 0, 0); + gtk_widget_show(save_gui.tiling_combo_box); + + save_gui.defaults_table = gtk_table_new(1, 3, FALSE); + gtk_table_set_col_spacings(GTK_TABLE(save_gui.defaults_table), 6); + gtk_box_pack_start(GTK_BOX(save_gui.vbox), save_gui.defaults_table, FALSE, FALSE, 0); + gtk_widget_show(save_gui.defaults_table); + + save_gui.defaults_button = gtk_button_new_with_mnemonic(_("_Load Defaults")); + gtk_table_attach(GTK_TABLE(save_gui.defaults_table), save_gui.defaults_button, 0, 1, 1, 2, GTK_FILL, (GtkAttachOptions)0, 0, 0); + gtk_widget_show(save_gui.defaults_button); + + g_signal_connect_swapped(save_gui.defaults_button, "clicked", G_CALLBACK(load_save_gui_defaults), &save_gui); + + gtk_widget_show(save_gui.dialog); + + dialog_result = gimp_dialog_run(GIMP_DIALOG(save_gui.dialog)) == GTK_RESPONSE_OK; + + save_options->image_quality = gtk_adjustment_get_value(GTK_ADJUSTMENT(save_gui.quality_entry)); + save_options->alpha_quality = gtk_adjustment_get_value(GTK_ADJUSTMENT(save_gui.alpha_quality_entry)); + save_options->overlap = gtk_combo_box_get_active(GTK_COMBO_BOX(save_gui.overlap_combo_box)); + save_options->subsampling = gtk_combo_box_get_active(GTK_COMBO_BOX(save_gui.subsampling_combo_box)); + save_options->tiling = gtk_combo_box_get_active(GTK_COMBO_BOX(save_gui.tiling_combo_box)); + + gtk_widget_destroy(save_gui.dialog); + + return dialog_result; +} + +static void load_save_gui_defaults(const SaveGui* save_gui) +{ + gtk_adjustment_set_value(GTK_ADJUSTMENT(save_gui->quality_entry), DEFAULT_SAVE_OPTIONS.image_quality); + gtk_adjustment_set_value(GTK_ADJUSTMENT(save_gui->alpha_quality_entry), DEFAULT_SAVE_OPTIONS.alpha_quality); + gtk_combo_box_set_active(GTK_COMBO_BOX(save_gui->overlap_combo_box), DEFAULT_SAVE_OPTIONS.overlap); + gtk_combo_box_set_active(GTK_COMBO_BOX(save_gui->subsampling_combo_box), DEFAULT_SAVE_OPTIONS.subsampling); + gtk_combo_box_set_active(GTK_COMBO_BOX(save_gui->tiling_combo_box), DEFAULT_SAVE_OPTIONS.tiling); +} + +/*static void open_help(const gchar* help_id, gpointer help_data) +{ +#ifdef _WIN32 + ShellExecute(NULL, "open", "http://gimpjpegxrplugin.codeplex.com/", NULL, NULL, SW_SHOWNORMAL); +#else + pid_t pid; + char *args[3]; + + args[0] = "/usr/bin/xdg-open"; + args[1] = "http://gimpjpegxrplugin.codeplex.com/"; + args[2] = NULL; + + pid = fork(); + + if (!pid) + execvp(args[0], args); +#endif +}*/ diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..6d34b10 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,283 @@ +#include "file-jxr.h" +#include + +guint get_bits_per_pixel(const PKPixelFormatGUID* pixel_format) +{ + PKPixelInfo pixel_info; + + pixel_info.pGUIDPixFmt = pixel_format; + + PixelFormatLookup(&pixel_info, LOOKUP_FORWARD); + + return pixel_info.cbitUnit; +} + +ERR convert_bw_indexed(const guchar* pixels, guint width, guint height, guchar** conv_pixels) +{ + ERR err; + guint stride; + const guchar* src; + guchar* dst; + const guchar* end; + guint n; + + err = PKAllocAligned(conv_pixels, width * height, 128); + + if (Failed(err)) + return err; + + stride = (width + 7) / 8; + + src = pixels; + dst = *conv_pixels; + end = pixels + height * stride; + + while (src < end) + { + const guchar* line_end = src + width / 8; + + while (src < line_end) + { + for (n = 0; n < 8; n++) + *(dst++) = (*src >> (7 - n)) & 0x01; + + src++; + } + + if (width % 8 != 0) + { + for (n = 0; n < width % 8; n++) + *(dst++) = (*src >> (7 - n)) & 0x01; + + src++; + } + } + + return err; +} + +void convert_indexed_bw(guchar* pixels, guint width, guint height) +{ + guint y; + guint x; + guint n; + + guchar* src = pixels; + guchar* dst = pixels; + + for (y = 0; y < height; y++) + { + for (x = 0; x < width / 8; x++) + { + *dst = (*src << 7) | (*(src + 1) << 6) | (*(src + 2) << 5) | (*(src + 3) << 4) | + (*(src + 4) << 3) | (*(src + 5) << 2) | (*(src + 6) << 1) | *(src + 7); + src += 8; + dst++; + } + + if (width % 8 != 0) + { + *dst = 0x00; + for (n = 0; n < width % 8; n++) + *dst |= *(src++) << (7 - n); + dst++; + } + } +} + +gboolean has_blackwhite_colormap(gint32 image_ID, gboolean* black_one) +{ + guchar* colormap; + gint num_colors; + gboolean bw = FALSE; + + colormap = gimp_image_get_colormap(image_ID, &num_colors); + + if (num_colors == 2) + { + if (memcmp(colormap, "\x00\x00\x00\xFF\xFF\xFF", 6) == 0) + { + bw = TRUE; + *black_one = FALSE; + } + else if (memcmp(colormap, "\xFF\xFF\xFF\x00\x00\x00", 6) == 0) + { + bw = TRUE; + *black_one = TRUE; + } + } + + g_free(colormap); + + return bw; +} + +gchar* get_pixel_format_mnemonic(const PKPixelFormatGUID* pixel_format) +{ + if (memcmp(pixel_format, "\x24\xC3\xDD\x6F\x03\x4E\xFE\x4B\xB1\x85\x3D\x77\x76\x8D\xC9", 15) != 0) + return NULL; + + switch (pixel_format->Data4[7]) + { + case 0x0D: + return "24bppRGB"; + case 0x0C: + return "24bppBGR"; + case 0x0E: + return "32bppBGR"; + case 0x15: + return "48bppRGB"; + case 0x12: + return "48bppRGBFixedPoint"; + case 0x3B: + return "48bppRGBHalf"; + case 0x18: + return "96bppRGBFixedPoint"; + case 0x40: + return "64bppRGBFixedPoint"; + case 0x42: + return "64bppRGBHalf"; + case 0x41: + return "128bppRGBFixedPoint"; + case 0x1B: + return "128bppRGBFloat"; + case 0x0F: + return "32bppBGRA"; + case 0x16: + return "64bppRGBA"; + case 0x1D: + return "64bppRGBAFixedPoint"; + case 0x3A: + return "64bppRGBAHalf"; + case 0x1E: + return "128bppRGBAFixedPoint"; + case 0x19: + return "128bppRGBAFloat"; + case 0x10: + return "32bppPBGRA"; + case 0x17: + return "64bppPRGBA"; + case 0x1A: + return "128bppPRGBAFloat"; + case 0x1C: + return "32bppCMYK"; + case 0x2C: + return "40bppCMYKAlpha"; + case 0x1F: + return "64bppCMYK"; + case 0x2D: + return "80bppCMYKAlpha"; + case 0x20: + return "24bpp3Channels"; + case 0x21: + return "32bpp4Channels"; + case 0x22: + return "40bpp5Channels"; + case 0x23: + return "48bpp6Channels"; + case 0x24: + return "56bpp7Channels"; + case 0x25: + return "64bpp8Channels"; + case 0x2E: + return "32bpp3ChannelsAlpha"; + case 0x2F: + return "40bpp4ChannelsAlpha"; + case 0x30: + return "48bpp5ChannelsAlpha"; + case 0x31: + return "56bpp6ChannelsAlpha"; + case 0x32: + return "64bpp7ChannelsAlpha"; + case 0x33: + return "72bpp8ChannelsAlpha"; + case 0x26: + return "48bpp3Channels"; + case 0x27: + return "64bpp4Channels"; + case 0x28: + return "80bpp5Channels"; + case 0x29: + return "96bpp6Channels"; + case 0x2A: + return "112bpp7Channels"; + case 0x2B: + return "128bpp8Channels"; + case 0x34: + return "64bpp3ChannelsAlpha"; + case 0x35: + return "80bpp4ChannelsAlpha"; + case 0x36: + return "96bpp5ChannelsAlpha"; + case 0x37: + return "112bpp6ChannelsAlpha"; + case 0x38: + return "128bpp7ChannelsAlpha"; + case 0x39: + return "144bpp8ChannelsAlpha"; + case 0x08: + return "8bppGray"; + case 0x0B: + return "16bppGray"; + case 0x13: + return "16bppGrayFixedPoint"; + case 0x3E: + return "16bppGrayHalf"; + case 0x3F: + return "32bppGrayFixedPoint"; + case 0x11: + return "32bppGrayFloat"; + case 0x05: + return "BlackWhite"; + case 0x09: + return "16bppBGR555"; + case 0x0A: + return "16bppBGR565"; + case 0x14: + return "32bppBGR101010"; + case 0x3D: + return "32bppRGBE"; + case 0x54: + return "32bppCMYKDIRECT"; + case 0x55: + return "64bppCMYKDIRECT"; + case 0x56: + return "40bppCMYKDIRECTAlpha"; + case 0x43: + return "80bppCMYKDIRECTAlpha"; + case 0x44: + return "12bppYCC420"; + case 0x45: + return "16bppYCC422"; + case 0x46: + return "20bppYCC422"; + case 0x47: + return "32bppYCC422"; + case 0x48: + return "24bppYCC444"; + case 0x49: + return "30bppYCC444"; + case 0x4A: + return "48bppYCC444"; + case 0x4B: + return "48bppYCC444FixedPoint"; + case 0x4C: + return "20bppYCC420Alpha"; + case 0x4D: + return "24bppYCC422Alpha"; + case 0x4E: + return "30bppYCC422Alpha"; + case 0x4F: + return "48bppYCC422Alpha"; + case 0x50: + return "32bppYCC444Alpha"; + case 0x51: + return "40bppYCC444Alpha"; + case 0x52: + return "64bppYCC444Alpha"; + case 0x53: + return "64bppYCC444AlphaFixedPoint"; + default: + return NULL; + } +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..9cb7e6a --- /dev/null +++ b/src/utils.h @@ -0,0 +1,13 @@ +#ifndef UTILS_H +#define UTILS_H + +#include "file-jxr.h" +#include + +guint get_bits_per_pixel(const PKPixelFormatGUID* pixel_format); +ERR convert_bw_indexed(const guchar* pixels, guint width, guint height, guchar** conv_pixels); +void convert_indexed_bw(guchar* pixels, guint width, guint height); +gboolean has_blackwhite_colormap(gint32 image_ID, gboolean* black_one); +gchar* get_pixel_format_mnemonic(const PKPixelFormatGUID* pixel_format); + +#endif