diff --git a/.gitignore b/.gitignore index f2c2724..4ce94a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,14 @@ *~ +build mms-engine/mms-engine.pro.user -mms-engine/build mms-lib/mms-lib.pro.user -mms-lib/build mms-lib/test/coverage/full.gcov mms-lib/test/coverage/mms-lib.gcov mms-lib/test/coverage/results -mms-lib/test/media_type/build -mms-lib/test/mms_codec/build mms-lib/test/mms_lib_test.ncb mms-lib/test/mms_lib_test.opt -mms-lib/test/read_report/build -mms-lib/test/retrieve/build -mms-lib/test/retrieve_cancel/build -mms-lib/test/retrieve_no_proxy/build mms-ofono/mms-ofono.pro.user -mms-ofono/build mms-handler-dbus/mms-handler-dbus.pro.user -mms-handler-dbus/build -mms-handler-dbus/test/mms_handler_dbus_server/build mms-handler-dbus/test/mms_handler_dbus_server/test_mms_handler_dbus_server.pro.user -mms-handler-dbus/test/mms_handler_dbus_client/build -mms-dump/build mms-dump/mms_dump.ncb mms-dump/mms_dump.opt -mms-send/build diff --git a/mms-engine/Makefile b/mms-engine/Makefile index d7fef45..9558fc6 100644 --- a/mms-engine/Makefile +++ b/mms-engine/Makefile @@ -90,7 +90,7 @@ endif DEBUG_CFLAGS = $(DEBUG_FLAGS) $(DEBUG_DEFS) $(CFLAGS) RELEASE_CFLAGS = $(RELEASE_FLAGS) $(RELEASE_DEFS) $(CFLAGS) -LIBS = $(shell pkg-config --libs $(LIB_PKGS)) -lmagic +LIBS = $(shell pkg-config --libs $(LIB_PKGS)) -lmagic -ljpeg DEBUG_LIBS = \ $(MMS_OFONO_DEBUG_LIB) \ $(MMS_HANDLER_DEBUG_LIB) \ diff --git a/mms-engine/main.c b/mms-engine/main.c index 3b1c7c8..36cb552 100644 --- a/mms-engine/main.c +++ b/mms-engine/main.c @@ -150,6 +150,7 @@ mms_app_parse_options( GError* error = NULL; gboolean session_bus = FALSE; gint size_limit_kb = opt->config.size_limit/1024; + gdouble megapixels = opt->config.max_pixels / 1000000.0; char* root_dir_help = g_strdup_printf( "Root directory for MMS files [%s]", opt->config.root_dir); @@ -162,6 +163,9 @@ mms_app_parse_options( char* size_limit_help = g_strdup_printf( "Maximum size for outgoing messages [%d]", size_limit_kb); + char* megapixels_help = g_strdup_printf( + "Maximum pixel count for outgoing images [%.1f]", + megapixels); char* description = mms_log_description(mms_app_log_modules, G_N_ELEMENTS(mms_app_log_modules)); @@ -177,6 +181,8 @@ mms_app_parse_options( &opt->config.idle_secs, idle_secs_help, "SEC" }, { "size-limit", 's', 0, G_OPTION_ARG_INT, &size_limit_kb, size_limit_help, "KB" }, + { "pix-limit", 'p', 0, G_OPTION_ARG_DOUBLE, + &megapixels, megapixels_help, "MPIX" }, { "keep-running", 'k', 0, G_OPTION_ARG_NONE, &opt->keep_running, "Keep running after everything is done", NULL }, { "keep-temp-files", 't', 0, G_OPTION_ARG_NONE, @@ -189,7 +195,7 @@ mms_app_parse_options( mms_app_option_verbose, "Be verbose (equivalent to -l=verbose)", NULL }, { "log-output", 'o', 0, G_OPTION_ARG_CALLBACK, mms_app_option_logtype, - "Log output [stdout]", "" }, + "Log output (stdout|syslog|glib) [stdout]", "TYPE" }, { "log-level", 'l', 0, G_OPTION_ARG_CALLBACK, mms_app_option_loglevel, "Set log level (repeatable)", "[MODULE:]LEVEL" }, { NULL } @@ -204,11 +210,21 @@ mms_app_parse_options( g_free(retry_secs_help); g_free(idle_secs_help); g_free(size_limit_help); + g_free(megapixels_help); g_free(description); - if (ok && size_limit_kb >= 0) { + if (ok) { MMS_INFO("Starting"); - opt->config.size_limit = size_limit_kb * 1024; + if (size_limit_kb >= 0) { + opt->config.size_limit = size_limit_kb * 1024; + } else { + opt->config.size_limit = 0; + } + if (megapixels >= 0) { + opt->config.max_pixels = (int)(megapixels*1000)*1000; + } else { + opt->config.max_pixels = 0; + } if (opt->dir) opt->config.root_dir = opt->dir; if (session_bus) { MMS_DEBUG("Attaching to session bus"); diff --git a/mms-engine/mms-engine.pro b/mms-engine/mms-engine.pro index c3f5f79..c8e58fb 100644 --- a/mms-engine/mms-engine.pro +++ b/mms-engine/mms-engine.pro @@ -35,7 +35,7 @@ CONFIG(debug, debug|release) { LIBS += $$MMS_LIB_DIR/build/release/libmms-lib.a } -LIBS += -lmagic +LIBS += -lmagic -ljpeg MMS_ENGINE_DBUS_XML = $$DBUS_INTERFACE_DIR/org.nemomobile.MmsEngine.xml MMS_ENGINE_DBUS_H = org.nemomobile.MmsEngine.h diff --git a/mms-lib/Makefile b/mms-lib/Makefile index d64adce..9fac10c 100644 --- a/mms-lib/Makefile +++ b/mms-lib/Makefile @@ -15,12 +15,13 @@ all: debug release # Sources # -SRC = mms_attachment.c mms_attachment_image.c mms_codec.c mms_connection.c \ - mms_connman.c mms_dispatcher.c mms_error.c mms_handler.c mms_lib_util.c \ - mms_file_util.c mms_log.c mms_message.c mms_task.c mms_task_ack.c \ - mms_task_decode.c mms_task_encode.c mms_task_http.c mms_task_notification.c \ - mms_task_notifyresp.c mms_task_publish.c mms_task_read.c \ - mms_task_retrieve.c mms_task_send.c mms_util.c +SRC = mms_attachment.c mms_attachment_image.c mms_attachment_jpeg.c \ + mms_codec.c mms_connection.c mms_connman.c mms_dispatcher.c mms_error.c \ + mms_handler.c mms_lib_util.c mms_file_util.c mms_log.c mms_message.c \ + mms_task.c mms_task_ack.c mms_task_decode.c mms_task_encode.c \ + mms_task_http.c mms_task_notification.c mms_task_notifyresp.c \ + mms_task_publish.c mms_task_read.c mms_task_retrieve.c mms_task_send.c \ + mms_util.c # # Directories diff --git a/mms-lib/include/mms_lib_types.h b/mms-lib/include/mms_lib_types.h index 4daa029..c33bb0c 100644 --- a/mms-lib/include/mms_lib_types.h +++ b/mms-lib/include/mms_lib_types.h @@ -50,6 +50,7 @@ typedef struct mms_config { int retry_secs; /* Retry timeout in seconds */ int idle_secs; /* Idle timeout */ gsize size_limit; /* Maximum size of m-Send.req PDU */ + guint max_pixels; /* Pixel limit for outbound images */ gboolean keep_temp_files; /* Keep temporary files around */ gboolean attic_enabled; /* Keep unrecognized push message in attic */ gboolean send_dr; /* Allow sending delivery reports */ diff --git a/mms-lib/mms-lib.pro b/mms-lib/mms-lib.pro index edc0a69..010b500 100644 --- a/mms-lib/mms-lib.pro +++ b/mms-lib/mms-lib.pro @@ -19,6 +19,7 @@ CONFIG(debug, debug|release) { SOURCES += \ src/mms_attachment.c \ src/mms_attachment_image.c \ + src/mms_attachment_jpeg.c \ src/mms_codec.c \ src/mms_connection.c \ src/mms_connman.c \ @@ -44,6 +45,7 @@ SOURCES += \ HEADERS += \ src/mms_attachment.h \ + src/mms_attachment_image.h \ src/mms_codec.h \ src/mms_error.h \ src/mms_file_util.h \ diff --git a/mms-lib/src/mms_attachment.c b/mms-lib/src/mms_attachment.c index 8eb9075..076163e 100644 --- a/mms-lib/src/mms_attachment.c +++ b/mms-lib/src/mms_attachment.c @@ -50,9 +50,10 @@ mms_attachment_finalize( GObject* object) { MMSAttachment* at = MMS_ATTACHMENT(object); + MMS_VERBOSE_("%p", at); g_mapped_file_unref(at->map); if (!at->config->keep_temp_files && - !(at->flags & MMS_ATTACHMENT_DONT_DELETE_FILES)) { + !(at->flags & MMS_ATTACHMENT_KEEP_FILES)) { char* dir = g_path_get_dirname(at->original_file); remove(at->original_file); rmdir(dir); @@ -76,9 +77,9 @@ mms_attachment_class_init( static void mms_attachment_init( - MMSAttachment* attachment) + MMSAttachment* at) { - MMS_VERBOSE_("%p", attachment); + MMS_VERBOSE_("%p", at); } static @@ -210,6 +211,7 @@ mms_attachment_new( if (map) { unsigned int flags = 0; char* content_type = NULL; + GType type; MMSAttachment* at; if (info->content_type) { @@ -274,9 +276,16 @@ mms_attachment_new( } MMS_DEBUG("%s: %s", path, content_type); - at = g_object_new(g_str_has_prefix(content_type, "image/") ? - MMS_TYPE_ATTACHMENT_IMAGE : MMS_TYPE_ATTACHMENT, NULL); + if (!strcmp(content_type, "image/jpeg")) { + type = MMS_TYPE_ATTACHMENT_JPEG; + } else if (g_str_has_prefix(content_type, "image/")) { + type = MMS_TYPE_ATTACHMENT_IMAGE; + } else { + type = MMS_TYPE_ATTACHMENT; + } + + at = g_object_new(type, NULL); at->config = config; at->map = map; at->flags |= flags; diff --git a/mms-lib/src/mms_attachment.h b/mms-lib/src/mms_attachment.h index d28340c..1dd7833 100644 --- a/mms-lib/src/mms_attachment.h +++ b/mms-lib/src/mms_attachment.h @@ -29,9 +29,9 @@ struct _mms_attachment { GMappedFile* map; /* Mapped attachment file */ unsigned int flags; /* Flags: */ -#define MMS_ATTACHMENT_SMIL (0x01) -#define MMS_ATTACHMENT_DONT_DELETE_FILES (0x02) -#define MMS_ATTACHMENT_RESIZABLE (0x04) +#define MMS_ATTACHMENT_SMIL (0x01) +#define MMS_ATTACHMENT_KEEP_FILES (0x02) +#define MMS_ATTACHMENT_RESIZABLE (0x04) }; @@ -43,10 +43,10 @@ typedef struct mms_attachment_class { GType mms_attachment_get_type(void); GType mms_attachment_image_get_type(void); -#define MMS_TYPE_ATTACHMENT (mms_attachment_get_type()) -#define MMS_TYPE_ATTACHMENT_IMAGE (mms_attachment_image_get_type()) -#define MMS_ATTACHMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ - MMS_TYPE_ATTACHMENT, MMSAttachmentClass)) +GType mms_attachment_jpeg_get_type(void); +#define MMS_TYPE_ATTACHMENT (mms_attachment_get_type()) +#define MMS_TYPE_ATTACHMENT_IMAGE (mms_attachment_image_get_type()) +#define MMS_TYPE_ATTACHMENT_JPEG (mms_attachment_jpeg_get_type()) MMSAttachment* mms_attachment_new( diff --git a/mms-lib/src/mms_attachment_image.c b/mms-lib/src/mms_attachment_image.c index a0532e5..bf9b410 100644 --- a/mms-lib/src/mms_attachment_image.c +++ b/mms-lib/src/mms_attachment_image.c @@ -12,7 +12,7 @@ * */ -#include "mms_attachment.h" +#include "mms_attachment_image.h" #include "mms_file_util.h" #ifdef HAVE_IMAGEMAGICK @@ -23,77 +23,89 @@ #define MMS_LOG_MODULE_NAME mms_attachment_log #include "mms_lib_log.h" -typedef MMSAttachmentClass MMSAttachmentImageClass; -typedef struct mma_attachment_image { - MMSAttachment attachment; - int resize_step; - char* resized; -} MMSAttachmentImage; - G_DEFINE_TYPE(MMSAttachmentImage, mms_attachment_image, MMS_TYPE_ATTACHMENT); - #define MMS_ATTACHMENT_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ MMS_TYPE_ATTACHMENT_IMAGE, MMSAttachmentImage)) -#define MMS_ATTACHMENT_IMAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ +#define MMS_ATTACHMENT_IMAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ MMS_TYPE_ATTACHMENT_IMAGE, MMSAttachmentImageClass)) static -gboolean -mms_attachment_image_resize( - MMSAttachment* at) +int +mms_attachment_image_next_resize_step( + MMSAttachmentImage* image, + unsigned int columns, + unsigned int rows) { - gboolean ok = FALSE; - -#ifdef HAVE_IMAGEMAGICK - MMSAttachmentImage* image = MMS_ATTACHMENT_IMAGE(at); - ExceptionInfo ex; - Image* src; - ImageInfo* info = CloneImageInfo(NULL); - GetExceptionInfo(&ex); - strncpy(info->filename, at->original_file, G_N_ELEMENTS(info->filename)); - info->filename[G_N_ELEMENTS(info->filename)-1] = 0; + int next_step = image->resize_step + 1; + if (image->attachment.config->max_pixels > 0) { + unsigned int size = (columns/(next_step+1))*(rows/(next_step+1)); + while (size > 0 && size > image->attachment.config->max_pixels) { + next_step++; + size = (columns/(next_step+1))*(rows/(next_step+1)); + } + } + return next_step; +} +static +const char* +mms_attachment_image_prepare_filename( + MMSAttachmentImage* image) +{ if (image->resized) { remove(image->resized); image->attachment.file_name = image->attachment.original_file; } else { - char* dir = g_path_get_dirname(at->original_file); + char* dir = g_path_get_dirname(image->attachment.original_file); char* subdir = g_strconcat(dir, "/" MMS_RESIZE_DIR, NULL); g_mkdir_with_parents(subdir, MMS_DIR_PERM); G_GNUC_BEGIN_IGNORE_DEPRECATIONS; image->resized = g_strconcat(subdir, "/", - g_basename(at->original_file), NULL); + g_basename(image->attachment.original_file), NULL); G_GNUC_END_IGNORE_DEPRECATIONS; g_free(dir); g_free(subdir); } + return image->resized; +} +#ifdef HAVE_IMAGEMAGICK +static +gboolean +mms_attachment_image_resize_imagemagick( + MMSAttachmentImage* image) +{ + gboolean ok = FALSE; + ExceptionInfo ex; + Image* src; + ImageInfo* info = CloneImageInfo(NULL); + const char* fname = mms_attachment_image_prepare_filename(image); + GetExceptionInfo(&ex); + strncpy(info->filename, image->attachment.original_file, + G_N_ELEMENTS(info->filename)); + info->filename[G_N_ELEMENTS(info->filename)-1] = 0; src = ReadImage(info, &ex); if (src) { if (src->magick_columns > 1 && src->magick_rows > 1) {; - const guint cols = src->magick_columns/(image->resize_step+2); - const guint rows = src->magick_rows/(image->resize_step+2); - Image* dest = ResizeImage(src, cols, rows, BoxFilter, 1.0, &ex); + const int next_step = mms_attachment_image_next_resize_step(image, + src->magick_columns, src->magick_rows); + const unsigned int src_cols = src->magick_columns; + const unsigned int src_rows = src->magick_rows; + const unsigned int cols = src_cols/(next_step+1); + const unsigned int rows = src_rows/(next_step+1); + Image* dest; + MMS_DEBUG("Resizing (%ux%u -> %ux%u) with ImageMagick", + src_cols, src_rows, cols, rows); + dest = ResizeImage(src, cols, rows, BoxFilter, 1.0, &ex); if (dest) { - const char* fname = image->resized; - image->resize_step++; + image->resize_step = next_step; strncpy(info->filename, fname, G_N_ELEMENTS(info->filename)); strncpy(dest->filename, fname, G_N_ELEMENTS(dest->filename)); info->filename[G_N_ELEMENTS(info->filename)-1] = 0; dest->filename[G_N_ELEMENTS(dest->filename)-1] = 0; if (WriteImage(info, dest)) { - GError* err = NULL; - GMappedFile* map = g_mapped_file_new(fname, FALSE, &err); - if (map) { - MMS_DEBUG("Resized %s (%ux%u)", fname, cols, rows); - image->attachment.file_name = fname; - if (at->map) g_mapped_file_unref(at->map); - at->map = map; - ok = TRUE; - } else { - MMS_ERR("%s", MMS_ERRMSG(err)); - g_error_free(err); - } + MMS_DEBUG("Resized %s with ImageMagick", fname); + ok = TRUE; } else { MMS_ERR("Failed to write %s", dest->filename); } @@ -107,8 +119,153 @@ mms_attachment_image_resize( ClearMagickException(&ex); DestroyExceptionInfo(&ex); DestroyImageInfo(info); -#endif + return ok; +} +#endif /* HAVE_IMAGEMAGICK */ + +static +gboolean +mms_attachment_image_resize_type_specific( + MMSAttachmentImage* image) +{ + /* If klass->fn_resize_new is not NULL, then we assume that all + * other callbacks are present as well */ + gboolean ok = FALSE; + MMSAttachment* at = &image->attachment; + MMSAttachmentImageClass* klass = MMS_ATTACHMENT_IMAGE_GET_CLASS(image); + MMSAttachmentImageResize* resize; + if (klass->fn_resize_new && (resize = + klass->fn_resize_new(image, at->original_file)) != NULL) { + gboolean can_resize; + const char* fname = mms_attachment_image_prepare_filename(image); + const int next_step = mms_attachment_image_next_resize_step(image, + resize->image.width, resize->image.height); + MMSAttachmentImageSize image_size; + MMSAttachmentImageSize out_size; + image_size = resize->image; + out_size.width = image_size.width/(next_step+1); + out_size.height = image_size.height/(next_step+1); + + resize->in = resize->out = out_size; + can_resize = klass->fn_resize_prepare(resize, fname); + if (!can_resize) { + klass->fn_resize_free(resize); + resize = klass->fn_resize_new(image, at->original_file); + if (!resize) return FALSE; + MMS_ASSERT(resize->image.width == image_size.width); + MMS_ASSERT(resize->image.height == image_size.height); + resize->in = image_size; + resize->out = out_size; + can_resize = klass->fn_resize_prepare(resize, fname); + } + + if (can_resize) { + unsigned char* line = g_malloc(3*resize->in.width); + guint y; + if (resize->in.width == resize->out.width && + resize->in.height == resize->out.height) { + /* Nothing to resize, image decompressor is doing all + * the job for us */ + MMS_DEBUG("Decoding (%ux%u -> %ux%u)", + image_size.width, image_size.height, + out_size.width, out_size.height); + for (y=0; + yin.height && + klass->fn_resize_read_line(resize, line) && + klass->fn_resize_write_line(resize, line); + y++); + } else { + const guint nx = (resize->in.width/resize->out.width); + const guint ny = (resize->in.height/resize->out.height); + gsize bufsize = 3*resize->out.width*sizeof(guint); + guint* buf = g_malloc(bufsize); + memset(buf, 0, bufsize); + MMS_DEBUG("Resizing (%ux%u -> %ux%u)", + image_size.width, image_size.height, + out_size.width, out_size.height); + for (y=0; + yin.height && + klass->fn_resize_read_line(resize, line); + y++) { + + /* Update the resize buffer */ + guint x; + guint* bufptr = buf; + const unsigned char* lineptr = line; + for (x=0; xout.width; x++) { + guint k; + for (k=0; kout.width; x++) { + (*outptr++) = (*bufptr++)/denominator; + (*outptr++) = (*bufptr++)/denominator; + (*outptr++) = (*bufptr++)/denominator; + } + + /* And write the next line */ + if (klass->fn_resize_write_line(resize, line)) { + memset(buf, 0, bufsize); + } else { + break; + } + } + } + g_free(buf); + } + + if (klass->fn_resize_finish) { + klass->fn_resize_finish(resize); + } + + if (y == resize->in.height) { + MMS_DEBUG("Resized %s", fname); + image->resize_step = next_step; + ok = TRUE; + } + g_free(line); + } + + klass->fn_resize_free(resize); + } + + return ok; +} + +static +gboolean +mms_attachment_image_resize( + MMSAttachment* at) +{ + MMSAttachmentImage* image = MMS_ATTACHMENT_IMAGE(at); + gboolean ok = mms_attachment_image_resize_type_specific(image); +#ifdef HAVE_IMAGEMAGICK + if (!ok) ok = mms_attachment_image_resize_imagemagick(image); +#endif /* HAVE_IMAGEMAGICK */ + if (ok) { + GError* error = NULL; + GMappedFile* map = g_mapped_file_new(image->resized, FALSE, &error); + if (map) { + at->file_name = image->resized; + if (at->map) g_mapped_file_unref(at->map); + at->map = map; + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + ok = FALSE; + } + } return ok; } @@ -119,11 +276,11 @@ mms_attachment_image_reset( { MMSAttachmentImage* image = MMS_ATTACHMENT_IMAGE(at); at->file_name = at->original_file; - if (image->resize_step > 0) { + if (image->resize_step) { + image->resize_step = 0; if (at->map) g_mapped_file_unref(at->map); at->map = g_mapped_file_new(at->original_file, FALSE, NULL); } - image->resize_step = 0; } static @@ -133,7 +290,7 @@ mms_attachment_image_finalize( { MMSAttachmentImage* image = MMS_ATTACHMENT_IMAGE(object); if (!image->attachment.config->keep_temp_files && - !(image->attachment.flags & MMS_ATTACHMENT_DONT_DELETE_FILES)) { + !(image->attachment.flags & MMS_ATTACHMENT_KEEP_FILES)) { mms_remove_file_and_dir(image->resized); } g_free(image->resized); @@ -145,8 +302,8 @@ void mms_attachment_image_class_init( MMSAttachmentImageClass* klass) { - klass->fn_reset = mms_attachment_image_reset; - klass->fn_resize = mms_attachment_image_resize; + klass->attachment.fn_reset = mms_attachment_image_reset; + klass->attachment.fn_resize = mms_attachment_image_resize; G_OBJECT_CLASS(klass)->finalize = mms_attachment_image_finalize; } @@ -155,7 +312,9 @@ void mms_attachment_image_init( MMSAttachmentImage* image) { +#ifdef HAVE_IMAGEMAGICK image->attachment.flags |= MMS_ATTACHMENT_RESIZABLE; +#endif } /* diff --git a/mms-lib/src/mms_attachment_image.h b/mms-lib/src/mms_attachment_image.h new file mode 100644 index 0000000..5bdb7f3 --- /dev/null +++ b/mms-lib/src/mms_attachment_image.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + */ + +#ifndef JOLLA_MMS_ATTACHMENT_IMAGE_H +#define JOLLA_MMS_ATTACHMENT_IMAGE_H + +#include "mms_attachment.h" + +typedef struct mms_attachment_image_size { + unsigned int width; + unsigned int height; +} MMSAttachmentImageSize; + +typedef struct mms_attachment_image_resize { + MMSAttachmentImageSize image; + MMSAttachmentImageSize in; + MMSAttachmentImageSize out; +} MMSAttachmentImageResize; + +typedef struct mms_attachment_image { + MMSAttachment attachment; + int resize_step; + char* resized; +} MMSAttachmentImage; + +typedef struct mms_attachment_image_class { + MMSAttachmentClass attachment; + + /* Creates the resize context, sets image size */ + MMSAttachmentImageResize* + (*fn_resize_new)( + MMSAttachmentImage* image, + const char* file); + + /* Prepares the resize context for writing, sets input size */ + gboolean + (*fn_resize_prepare)( + MMSAttachmentImageResize* resize, + const char* file); + + /* Reads the next scanline in RGB24 format */ + gboolean + (*fn_resize_read_line)( + MMSAttachmentImageResize* resize, + unsigned char* rgb24); + + /* Writes the next scanline in RGB24 format */ + gboolean + (*fn_resize_write_line)( + MMSAttachmentImageResize* resize, + const unsigned char* rgb24); + + /* Finishes resizing */ + void (*fn_resize_finish)( + MMSAttachmentImageResize* resize); + + /* Frees the resize context */ + void (*fn_resize_free)( + MMSAttachmentImageResize* resize); + +} MMSAttachmentImageClass; + +#endif /* JOLLA_MMS_ATTACHMENT_IMAGE_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_attachment_jpeg.c b/mms-lib/src/mms_attachment_jpeg.c new file mode 100644 index 0000000..aed17ea --- /dev/null +++ b/mms-lib/src/mms_attachment_jpeg.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + */ + +#include "mms_attachment_image.h" +#include "mms_file_util.h" + +#include +#include +#include + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_attachment_log +#include "mms_lib_log.h" + +typedef MMSAttachmentImageClass MMSAttachmentJpegClass; +typedef MMSAttachmentImage MMSAttachmentJpeg; + +G_DEFINE_TYPE(MMSAttachmentJpeg, mms_attachment_jpeg, \ + MMS_TYPE_ATTACHMENT_IMAGE); +#define MMS_ATTACHMENT_JPEG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + MMS_TYPE_ATTACHMENT_JPEG, MMSAttachmentJpeg)) + +typedef struct mms_attachment_jpeg_error { + struct jpeg_error_mgr pub; + jmp_buf setjmp_buf; +} MMSAttachmentJpegError; + +typedef struct mms_attachment_jpeg_resize { + MMSAttachmentImageResize pub; + MMSAttachmentJpegError err; + struct jpeg_decompress_struct decomp; + struct jpeg_compress_struct comp; + FILE* in; + FILE* out; +} MMSAttachmentJpegResize; + +static inline MMSAttachmentJpegResize* +mms_attachment_jpeg_resize_cast(MMSAttachmentImageResize* resize) + { return MMS_CAST(resize, MMSAttachmentJpegResize, pub); } + +static +void +mms_attachment_jpeg_error_log( + int level, + j_common_ptr cinfo) +{ + char* buf = g_malloc(JMSG_LENGTH_MAX); + buf[0] = 0; + cinfo->err->format_message(cinfo, buf); + buf[JMSG_LENGTH_MAX-1] = 0; + mms_log(MMS_LOG_MODULE_CURRENT, level, "%s", buf); + g_free(buf); +} + +static +void +mms_attachment_jpeg_error_exit( + j_common_ptr cinfo) +{ + MMSAttachmentJpegError* err = (MMSAttachmentJpegError*)cinfo->err; + mms_attachment_jpeg_error_log(MMS_LOGLEVEL_WARN, cinfo); + longjmp(err->setjmp_buf, 1); +} + +static +void +mms_attachment_jpeg_error_output( + j_common_ptr cinfo) +{ + mms_attachment_jpeg_error_log(MMS_LOGLEVEL_DEBUG, cinfo); +} + +static +MMSAttachmentImageResize* +mms_attachment_jpeg_resize_new( + MMSAttachmentImage* image, + const char* file) +{ + MMSAttachmentJpegResize* jpeg = g_new0(MMSAttachmentJpegResize, 1); + jpeg->in = fopen(file, "rb"); + if (jpeg->in) { + jpeg->decomp.err = jpeg_std_error(&jpeg->err.pub); + jpeg->err.pub.error_exit = mms_attachment_jpeg_error_exit; + jpeg->err.pub.output_message = mms_attachment_jpeg_error_output; + if (!setjmp(jpeg->err.setjmp_buf)) { + int i; + jpeg_create_decompress(&jpeg->decomp); + jpeg_save_markers(&jpeg->decomp, JPEG_COM, 0xFFFF); + for (i=0; i<16; i++) { + jpeg_save_markers(&jpeg->decomp, JPEG_APP0+i, 0xFFFF); + } + jpeg_stdio_src(&jpeg->decomp, jpeg->in); + jpeg_read_header(&jpeg->decomp, TRUE); + jpeg->pub.image.width = jpeg->decomp.image_width; + jpeg->pub.image.height = jpeg->decomp.image_height; + jpeg->pub.in = jpeg->pub.image; + return &jpeg->pub; + } + jpeg_destroy_decompress(&jpeg->decomp); + fclose(jpeg->in); + } + g_free(jpeg); + return NULL; +} + +static +gboolean +mms_attachment_jpeg_resize_prepare( + MMSAttachmentImageResize* resize, + const char* file) +{ + MMSAttachmentJpegResize* jpeg = mms_attachment_jpeg_resize_cast(resize); + jpeg->out = fopen(file, "wb"); + if (jpeg->out) { + jpeg->comp.err = &jpeg->err.pub; + if (!setjmp(jpeg->err.setjmp_buf)) { + jpeg_saved_marker_ptr marker; + jpeg_create_compress(&jpeg->comp); + + jpeg->decomp.scale_num = resize->in.width; + jpeg->decomp.scale_denom = resize->image.width; + jpeg->decomp.out_color_space = JCS_RGB; + jpeg_start_decompress(&jpeg->decomp); + + if (jpeg->decomp.output_width == resize->in.width && + jpeg->decomp.output_height == resize->in.height) { + + jpeg->comp.image_width = resize->out.width; + jpeg->comp.image_height = resize->out.height; + jpeg->comp.input_components = 3; + jpeg->comp.in_color_space = JCS_RGB; + + jpeg_stdio_dest(&jpeg->comp, jpeg->out); + jpeg_set_defaults(&jpeg->comp); + jpeg_set_quality(&jpeg->comp, 90, TRUE); + + jpeg->comp.write_JFIF_header = jpeg->decomp.saw_JFIF_marker; + jpeg_start_compress(&jpeg->comp, TRUE); + + for (marker = jpeg->decomp.marker_list; + marker != NULL; + marker = marker->next) { + /* Avoid duplicating markers */ + if (jpeg->comp.write_JFIF_header && + marker->marker == JPEG_APP0 && + marker->data_length >= 5 && + memcmp("JFIF", marker->data, 5) == 0) { + continue; + } + if (jpeg->comp.write_Adobe_marker && + marker->marker == JPEG_APP0+14 && + marker->data_length >= 5 && + memcmp("Adobe", marker->data, 5) == 0) { + continue; + } + jpeg_write_marker(&jpeg->comp, marker->marker, + marker->data, marker->data_length); + } + + return TRUE; + } + } + } + return FALSE; +} + +static +gboolean +mms_attachment_jpeg_read_line( + MMSAttachmentImageResize* resize, + unsigned char* rgb24) +{ + MMSAttachmentJpegResize* jpeg = mms_attachment_jpeg_resize_cast(resize); + if (!setjmp(jpeg->err.setjmp_buf)) { + JSAMPROW row = rgb24; + jpeg_read_scanlines(&jpeg->decomp, &row, 1); + return TRUE; + } + return FALSE; +} + +static +gboolean +mms_attachment_jpeg_write_line( + MMSAttachmentImageResize* resize, + const unsigned char* rgb24) +{ + MMSAttachmentJpegResize* jpeg = mms_attachment_jpeg_resize_cast(resize); + if (!setjmp(jpeg->err.setjmp_buf)) { + JSAMPROW row = (void*)rgb24; + jpeg_write_scanlines(&jpeg->comp, &row, 1); + return TRUE; + } + return FALSE; +} + +static +void +mms_attachment_jpeg_resize_finish( + MMSAttachmentImageResize* resize) +{ + MMSAttachmentJpegResize* jpeg = mms_attachment_jpeg_resize_cast(resize); + if (!setjmp(jpeg->err.setjmp_buf)) { + jpeg_finish_compress(&jpeg->comp); + jpeg_finish_decompress(&jpeg->decomp); + } +} + +static +void +mms_attachment_jpeg_resize_free( + MMSAttachmentImageResize* resize) +{ + MMSAttachmentJpegResize* jpeg = mms_attachment_jpeg_resize_cast(resize); + jpeg_destroy_compress(&jpeg->comp); + jpeg_destroy_decompress(&jpeg->decomp); + fclose(jpeg->in); + fclose(jpeg->out); + g_free(jpeg); +} + +static +void +mms_attachment_jpeg_class_init( + MMSAttachmentJpegClass* klass) +{ + klass->fn_resize_new = mms_attachment_jpeg_resize_new; + klass->fn_resize_prepare = mms_attachment_jpeg_resize_prepare; + klass->fn_resize_read_line = mms_attachment_jpeg_read_line; + klass->fn_resize_write_line = mms_attachment_jpeg_write_line; + klass->fn_resize_finish = mms_attachment_jpeg_resize_finish; + klass->fn_resize_free = mms_attachment_jpeg_resize_free; +} + +static +void +mms_attachment_jpeg_init( + MMSAttachmentJpeg* jpeg) +{ + jpeg->attachment.flags |= MMS_ATTACHMENT_RESIZABLE; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_lib_util.c b/mms-lib/src/mms_lib_util.c index 9dba245..a7299f3 100644 --- a/mms-lib/src/mms_lib_util.c +++ b/mms-lib/src/mms_lib_util.c @@ -27,6 +27,7 @@ #define MMS_DEFAULT_RETRY_SECS (15) #define MMS_DEFAULT_IDLE_SECS (20) #define MMS_DEFAULT_SIZE_LIMIT (300*1024) +#define MMS_DEFAULT_MAX_PIXELS (3000000) /** * MMS error domain @@ -78,6 +79,7 @@ mms_lib_default_config( config->retry_secs = MMS_DEFAULT_RETRY_SECS; config->idle_secs = MMS_DEFAULT_IDLE_SECS; config->size_limit = MMS_DEFAULT_SIZE_LIMIT; + config->max_pixels = MMS_DEFAULT_MAX_PIXELS; config->keep_temp_files = FALSE; config->attic_enabled = FALSE; config->send_dr = TRUE; diff --git a/mms-lib/test/common/Makefile b/mms-lib/test/common/Makefile index 2bcd3f4..3bc6829 100644 --- a/mms-lib/test/common/Makefile +++ b/mms-lib/test/common/Makefile @@ -18,8 +18,8 @@ endif # # Required packages # -LIB_PKGS = libwspcodec libsoup-2.4 glib-2.0 ImageMagick -PKGS = $(LIB_PKGS) +LIB_PKGS += libwspcodec libsoup-2.4 glib-2.0 ImageMagick +PKGS += $(LIB_PKGS) # # Default target diff --git a/mms-lib/test/coverage/run b/mms-lib/test/coverage/run index 86217e4..3c6cb8f 100755 --- a/mms-lib/test/coverage/run +++ b/mms-lib/test/coverage/run @@ -3,7 +3,7 @@ # This script requires lcov to be installed # -TESTS="media_type mms_codec read_report retrieve retrieve_cancel retrieve_no_proxy" +TESTS="media_type mms_codec read_report resize retrieve retrieve_cancel retrieve_no_proxy" FLAVOR="release" pushd `dirname $0` > /dev/null diff --git a/mms-lib/test/resize/Makefile b/mms-lib/test/resize/Makefile new file mode 100644 index 0000000..00d87f4 --- /dev/null +++ b/mms-lib/test/resize/Makefile @@ -0,0 +1,7 @@ +# -*- Mode: makefile-gmake -*- + +EXE = test_resize +SRC = test_resize.c +LIB_PKGS = libexif libpng + +include ../common/Makefile diff --git a/mms-lib/test/resize/data/0001.jpg b/mms-lib/test/resize/data/0001.jpg new file mode 100644 index 0000000..ac86411 Binary files /dev/null and b/mms-lib/test/resize/data/0001.jpg differ diff --git a/mms-lib/test/resize/data/0002.jpg b/mms-lib/test/resize/data/0002.jpg new file mode 100644 index 0000000..9520ed0 Binary files /dev/null and b/mms-lib/test/resize/data/0002.jpg differ diff --git a/mms-lib/test/resize/data/0003.png b/mms-lib/test/resize/data/0003.png new file mode 100644 index 0000000..9f8fb0f Binary files /dev/null and b/mms-lib/test/resize/data/0003.png differ diff --git a/mms-lib/test/resize/test_resize.c b/mms-lib/test/resize/test_resize.c new file mode 100644 index 0000000..cf730b4 --- /dev/null +++ b/mms-lib/test/resize/test_resize.c @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + */ + +#include "mms_attachment.h" +#include "mms_lib_util.h" +#include "mms_log.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define RET_OK (0) +#define RET_ERR (1) + +typedef struct test_size { + unsigned int width; + unsigned int height; +} TestSize; + +typedef struct test_image_type { + const char* content_type; + gboolean (*filesize)(const char* file, TestSize* size); +} TestImageType; + +typedef struct test_desc { + const char* name; + const char* file; + const TestImageType* type; + int steps; + TestSize size; +} TestDesc; + +typedef struct test_jpeg_error { + struct jpeg_error_mgr pub; + jmp_buf setjmp_buf; +} TestJpegError; + +typedef enum exif_orientation { + EXIF_ORIENTATION_UPPER_LEFT = 1, + EXIF_ORIENTATION_LOWER_RIGHT = 3, + EXIF_ORIENTATION_UPPER_RIGHT = 6, + EXIF_ORIENTATION_LOWER_LEFT = 8 +} EXIF_ORIENTATION; + +typedef struct test_jpeg_decompress { + struct jpeg_decompress_struct pub; + EXIF_ORIENTATION orientation; +} TestJpegDecompress; + +static +void +test_jpeg_error_log( + int level, + j_common_ptr cinfo) +{ + char* buf = g_malloc(JMSG_LENGTH_MAX); + buf[0] = 0; + cinfo->err->format_message(cinfo, buf); + buf[JMSG_LENGTH_MAX] = 0; + mms_log(NULL, level, "%s", buf); + g_free(buf); +} + +static +void +test_jpeg_error_exit( + j_common_ptr cinfo) +{ + TestJpegError* err = (TestJpegError*)cinfo->err; + test_jpeg_error_log(MMS_LOGLEVEL_ERR, cinfo); + longjmp(err->setjmp_buf, 1); +} + +static +void +test_jpeg_error_output( + j_common_ptr cinfo) +{ + test_jpeg_error_log(MMS_LOGLEVEL_DEBUG, cinfo); +} + +static +JOCTET +test_jpeg_getc( + j_decompress_ptr cinfo) +{ + struct jpeg_source_mgr* src = cinfo->src; + if (!src->bytes_in_buffer && !src->fill_input_buffer(cinfo)) { + ERREXIT(cinfo, JERR_CANT_SUSPEND); + } + src->bytes_in_buffer--; + return *src->next_input_byte++; +} + +static +boolean +test_jpeg_APP1( + j_decompress_ptr cinfo) +{ + TestJpegDecompress* dec = MMS_CAST(cinfo, TestJpegDecompress, pub); + ExifLoader* eloader; + ExifData* edata; + unsigned int len; + + /* Read the marker length */ + unsigned char buf[2]; + buf[0] = test_jpeg_getc(cinfo); + buf[1] = test_jpeg_getc(cinfo); + len = buf[0] << 8; + len += buf[1]; + MMS_DEBUG("Marker 0x%02X %u bytes", cinfo->unread_marker, len); + if (len < 2) ERREXIT(cinfo, JERR_BAD_LENGTH); + + /* Feed the whole thing to the Exit loader */ + eloader = exif_loader_new(); + exif_loader_write(eloader, buf, sizeof(buf)); + len -= 2; + while (len > 0) { + struct jpeg_source_mgr* src = cinfo->src; + if (src->bytes_in_buffer || src->fill_input_buffer(cinfo)) { + unsigned int nbytes = MIN(src->bytes_in_buffer, len); + exif_loader_write(eloader, (void*)src->next_input_byte, nbytes); + src->bytes_in_buffer -= nbytes; + src->next_input_byte += nbytes; + len -= nbytes; + } else { + ERREXIT(cinfo, JERR_CANT_SUSPEND); + } + } + edata = exif_loader_get_data(eloader); + exif_loader_unref(eloader); + if (edata) { + ExifEntry* orientation = exif_content_get_entry( + edata->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION); + if (orientation) { + /* Actually there are two bytes there but the second one + * should be zero */ + dec->orientation = orientation->data[0]; + MMS_DEBUG("Orientation %d", dec->orientation); + } + exif_data_unref(edata); + } + return TRUE; +} + +static +gboolean +test_jpeg_size( + const char* file, + TestSize* size) +{ + gboolean ok = FALSE; + FILE* in = fopen(file, "rb"); + if (in) { + TestJpegError err; + TestJpegDecompress dec; + dec.orientation = EXIF_ORIENTATION_UPPER_LEFT; + dec.pub.err = jpeg_std_error(&err.pub); + err.pub.error_exit = test_jpeg_error_exit; + err.pub.output_message = test_jpeg_error_output; + if (!setjmp(err.setjmp_buf)) { + jpeg_create_decompress(&dec.pub); + jpeg_set_marker_processor(&dec.pub, JPEG_APP0+1, test_jpeg_APP1); + jpeg_stdio_src(&dec.pub, in); + jpeg_read_header(&dec.pub, TRUE); + switch (dec.orientation) { + default: + case EXIF_ORIENTATION_UPPER_LEFT: + case EXIF_ORIENTATION_LOWER_RIGHT: + size->width = dec.pub.image_width; + size->height = dec.pub.image_height; + break; + case EXIF_ORIENTATION_UPPER_RIGHT: + case EXIF_ORIENTATION_LOWER_LEFT: + size->width = dec.pub.image_height; + size->height = dec.pub.image_width; + break; + } + ok = TRUE; + } + jpeg_destroy_decompress(&dec.pub); + fclose(in); + } + return ok; +} + +static +gboolean +test_png_size( + const char* file, + TestSize* size) +{ + gboolean ok = FALSE; + FILE* in = fopen(file, "rb"); + if (in) { + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!setjmp(png_jmpbuf(png_ptr))) { + png_init_io(png_ptr, in); + png_read_info(png_ptr, info_ptr); + size->width = png_get_image_width(png_ptr, info_ptr); + size->height = png_get_image_height(png_ptr, info_ptr); + ok = TRUE; + } + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + } + return ok; +} + +static +int +test_run_one( + const MMSConfig* config, + const TestDesc* test) +{ + int ret = RET_ERR; + char* tmpl; + const char* dir; + char* name = g_path_get_basename(test->file); + tmpl = g_strconcat(config->root_dir, "/resize_XXXXXX", NULL); + dir = g_mkdtemp(tmpl); + if (dir) { + GError* error = NULL; + char* testfile = g_strconcat(dir, "/", name, NULL); + GFile* src = g_file_new_for_path(test->file); + GFile* dest = g_file_new_for_path(testfile); + if (g_file_copy(src, dest, 0, NULL, NULL, NULL, &error)) { + MMSAttachment* at; + MMSAttachmentInfo info; + info.file_name = testfile; + info.content_type = test->type->content_type; + info.content_id = name; + at = mms_attachment_new(config, &info, &error); + if (at) { + int i; + gboolean ok = TRUE; + for (i=0; isteps && ok; i++) { + if (!mms_attachment_resize(at)) { + ok = FALSE; + } + } + if (ok) { + TestSize size; + if (test->type->filesize(at->file_name, &size)) { + if (size.width == test->size.width && + size.height == test->size.height) { + mms_attachment_reset(at); + if (!strcmp(at->file_name, testfile)) { + ret = RET_OK; + } else { + MMS_DEBUG("Reset didn't work"); + } + } else { + MMS_ERR("Output size mismatch: (%ux%u) vs (%ux%u)", + size.width, size.height, + test->size.width, test->size.height); + } + } + } + mms_attachment_unref(at); + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + } + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + } + g_object_unref(src); + g_object_unref(dest); + g_free(testfile); + } + MMS_INFO("%s: %s", (ret == RET_OK) ? "OK" : "FAILED", test->name); + g_free(name); + g_free(tmpl); + return ret; +} + +static +int +test_run( + const MMSConfig* config, + const char* name) +{ + static const TestImageType test_jpeg = + { "image/jpeg", test_jpeg_size }; + + static const TestImageType test_auto = + { NULL, test_jpeg_size }; + + static const TestImageType test_png = + { "image/png", test_png_size }; + + static const TestDesc resize_tests[] = { + { "Jpeg_Portrait1", "data/0001.jpg", &test_jpeg, 1, { 920, 1632 } }, + { "Jpeg_Portrait2", "data/0001.jpg", &test_jpeg, 2, { 613, 1088 } }, + { "Jpeg_Portrait3", "data/0001.jpg", &test_jpeg, 3, { 460, 816 } }, + { "Jpeg_Landscape1", "data/0002.jpg", &test_auto, 1, { 1632, 920 } }, + { "Jpeg_Landscape2", "data/0002.jpg", &test_auto, 2, { 1088, 613 } }, + { "Jpeg_Landscape3", "data/0002.jpg", &test_auto, 3, { 816, 460 } }, + { "Png_1", "data/0003.png", &test_png, 1, { 1000, 750 } }, + { "Png_2", "data/0003.png", &test_png, 2, { 666, 500 } }, + { "Png_3", "data/0003.png", &test_png, 3, { 500, 375 } }, + }; + + int i, ret; + if (name) { + const TestDesc* found = NULL; + for (i=0, ret = RET_ERR; ifile, name)) { + ret = test_run_one(config, test); + found = test; + break; + } + } + if (!found) MMS_ERR("No such test: %s", name); + } else { + for (i=0, ret = RET_OK; i