/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include "core-types.h" #include "config/pikadialogconfig.h" #include "vectors/pikavectors.h" #include "pika.h" #include "pikacontainer.h" #include "pikacontext.h" #include "pikaguide.h" #include "pikaimage.h" #include "pikaimage-flip.h" #include "pikaimage-metadata.h" #include "pikaimage-rotate.h" #include "pikaimage-guides.h" #include "pikaimage-sample-points.h" #include "pikaimage-undo.h" #include "pikaimage-undo-push.h" #include "pikaitem.h" #include "pikalayer.h" #include "pikaobjectqueue.h" #include "pikaprogress.h" #include "pikasamplepoint.h" static void pika_image_rotate_item_offset (PikaImage *image, PikaRotationType rotate_type, PikaItem *item, gint off_x, gint off_y); static void pika_image_rotate_guides (PikaImage *image, PikaRotationType rotate_type); static void pika_image_rotate_sample_points (PikaImage *image, PikaRotationType rotate_type); static void pika_image_metadata_rotate (PikaImage *image, PikaContext *context, GExiv2Orientation orientation, PikaProgress *progress); /* Public Functions */ void pika_image_rotate (PikaImage *image, PikaContext *context, PikaRotationType rotate_type, PikaProgress *progress) { PikaObjectQueue *queue; PikaItem *item; GList *list; gdouble center_x; gdouble center_y; gint new_image_width; gint new_image_height; gint previous_image_width; gint previous_image_height; gint offset_x; gint offset_y; gboolean size_changed; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_CONTEXT (context)); g_return_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress)); previous_image_width = pika_image_get_width (image); previous_image_height = pika_image_get_height (image); center_x = previous_image_width / 2.0; center_y = previous_image_height / 2.0; /* Resize the image (if needed) */ switch (rotate_type) { case PIKA_ROTATE_90: case PIKA_ROTATE_270: new_image_width = pika_image_get_height (image); new_image_height = pika_image_get_width (image); size_changed = TRUE; offset_x = (pika_image_get_width (image) - new_image_width) / 2; offset_y = (pika_image_get_height (image) - new_image_height) / 2; break; case PIKA_ROTATE_180: new_image_width = pika_image_get_width (image); new_image_height = pika_image_get_height (image); size_changed = FALSE; offset_x = 0; offset_y = 0; break; default: g_return_if_reached (); return; } pika_set_busy (image->pika); queue = pika_object_queue_new (progress); progress = PIKA_PROGRESS (queue); pika_object_queue_push_container (queue, pika_image_get_layers (image)); pika_object_queue_push (queue, pika_image_get_mask (image)); pika_object_queue_push_container (queue, pika_image_get_channels (image)); pika_object_queue_push_container (queue, pika_image_get_vectors (image)); g_object_freeze_notify (G_OBJECT (image)); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_IMAGE_ROTATE, NULL); /* Rotate all layers, channels (including selection mask), and vectors */ while ((item = pika_object_queue_pop (queue))) { gint off_x; gint off_y; pika_item_get_offset (item, &off_x, &off_y); pika_item_rotate (item, context, rotate_type, center_x, center_y, FALSE); if (PIKA_IS_LAYER (item)) { pika_image_rotate_item_offset (image, rotate_type, item, off_x, off_y); } else { pika_item_set_offset (item, 0, 0); if (PIKA_IS_VECTORS (item)) { pika_item_set_size (item, new_image_width, new_image_height); pika_item_translate (item, (new_image_width - pika_image_get_width (image)) / 2, (new_image_height - pika_image_get_height (image)) / 2, FALSE); } } pika_progress_set_value (progress, 1.0); } /* Rotate all Guides */ pika_image_rotate_guides (image, rotate_type); /* Rotate all sample points */ pika_image_rotate_sample_points (image, rotate_type); /* Resize the image (if needed) */ if (size_changed) { gdouble xres; gdouble yres; pika_image_undo_push_image_size (image, NULL, offset_x, offset_y, new_image_width, new_image_height); g_object_set (image, "width", new_image_width, "height", new_image_height, NULL); pika_image_get_resolution (image, &xres, &yres); if (xres != yres) pika_image_set_resolution (image, yres, xres); } /* Notify guide movements */ for (list = pika_image_get_guides (image); list; list = g_list_next (list)) { pika_image_guide_moved (image, list->data); } /* Notify sample point movements */ for (list = pika_image_get_sample_points (image); list; list = g_list_next (list)) { pika_image_sample_point_moved (image, list->data); } pika_image_undo_group_end (image); g_object_unref (queue); if (size_changed) pika_image_size_changed_detailed (image, -offset_x, -offset_y, previous_image_width, previous_image_height); g_object_thaw_notify (G_OBJECT (image)); pika_unset_busy (image->pika); } void pika_image_import_rotation_metadata (PikaImage *image, PikaContext *context, PikaProgress *progress, gboolean interactive) { PikaMetadata *metadata; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_CONTEXT (context)); g_return_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress)); metadata = pika_image_get_metadata (image); if (metadata) { PikaMetadataRotationPolicy policy; policy = PIKA_DIALOG_CONFIG (image->pika->config)->metadata_rotation_policy; if (policy == PIKA_METADATA_ROTATION_POLICY_ASK) { if (interactive) { gboolean dont_ask = FALSE; policy = pika_query_rotation_policy (image->pika, image, context, &dont_ask); if (dont_ask) { g_object_set (G_OBJECT (image->pika->config), "metadata-rotation-policy", policy, NULL); } } else { policy = PIKA_METADATA_ROTATION_POLICY_ROTATE; } } if (policy == PIKA_METADATA_ROTATION_POLICY_ROTATE) pika_image_metadata_rotate (image, context, gexiv2_metadata_try_get_orientation (GEXIV2_METADATA (metadata), NULL), progress); gexiv2_metadata_try_set_orientation (GEXIV2_METADATA (metadata), GEXIV2_ORIENTATION_NORMAL, NULL); } } /* Private Functions */ static void pika_image_rotate_item_offset (PikaImage *image, PikaRotationType rotate_type, PikaItem *item, gint off_x, gint off_y) { gint x = 0; gint y = 0; switch (rotate_type) { case PIKA_ROTATE_90: x = pika_image_get_height (image) - off_y - pika_item_get_width (item); y = off_x; break; case PIKA_ROTATE_270: x = off_y; y = pika_image_get_width (image) - off_x - pika_item_get_height (item); break; case PIKA_ROTATE_180: return; default: g_return_if_reached (); } pika_item_get_offset (item, &off_x, &off_y); x -= off_x; y -= off_y; if (x || y) pika_item_translate (item, x, y, FALSE); } static void pika_image_rotate_guides (PikaImage *image, PikaRotationType rotate_type) { GList *list; /* Rotate all Guides */ for (list = pika_image_get_guides (image); list; list = g_list_next (list)) { PikaGuide *guide = list->data; PikaOrientationType orientation = pika_guide_get_orientation (guide); gint position = pika_guide_get_position (guide); switch (rotate_type) { case PIKA_ROTATE_90: switch (orientation) { case PIKA_ORIENTATION_HORIZONTAL: pika_image_undo_push_guide (image, NULL, guide); pika_guide_set_orientation (guide, PIKA_ORIENTATION_VERTICAL); pika_guide_set_position (guide, pika_image_get_height (image) - position); break; case PIKA_ORIENTATION_VERTICAL: pika_image_undo_push_guide (image, NULL, guide); pika_guide_set_orientation (guide, PIKA_ORIENTATION_HORIZONTAL); break; default: break; } break; case PIKA_ROTATE_180: switch (orientation) { case PIKA_ORIENTATION_HORIZONTAL: pika_image_move_guide (image, guide, pika_image_get_height (image) - position, TRUE); break; case PIKA_ORIENTATION_VERTICAL: pika_image_move_guide (image, guide, pika_image_get_width (image) - position, TRUE); break; default: break; } break; case PIKA_ROTATE_270: switch (orientation) { case PIKA_ORIENTATION_HORIZONTAL: pika_image_undo_push_guide (image, NULL, guide); pika_guide_set_orientation (guide, PIKA_ORIENTATION_VERTICAL); break; case PIKA_ORIENTATION_VERTICAL: pika_image_undo_push_guide (image, NULL, guide); pika_guide_set_orientation (guide, PIKA_ORIENTATION_HORIZONTAL); pika_guide_set_position (guide, pika_image_get_width (image) - position); break; default: break; } break; } } } static void pika_image_rotate_sample_points (PikaImage *image, PikaRotationType rotate_type) { GList *list; /* Rotate all sample points */ for (list = pika_image_get_sample_points (image); list; list = g_list_next (list)) { PikaSamplePoint *sample_point = list->data; gint old_x; gint old_y; pika_image_undo_push_sample_point (image, NULL, sample_point); pika_sample_point_get_position (sample_point, &old_x, &old_y); switch (rotate_type) { case PIKA_ROTATE_90: pika_sample_point_set_position (sample_point, pika_image_get_height (image) - old_y, old_x); break; case PIKA_ROTATE_180: pika_sample_point_set_position (sample_point, pika_image_get_width (image) - old_x, pika_image_get_height (image) - old_y); break; case PIKA_ROTATE_270: pika_sample_point_set_position (sample_point, old_y, pika_image_get_width (image) - old_x); break; } } } static void pika_image_metadata_rotate (PikaImage *image, PikaContext *context, GExiv2Orientation orientation, PikaProgress *progress) { switch (orientation) { case GEXIV2_ORIENTATION_UNSPECIFIED: case GEXIV2_ORIENTATION_NORMAL: /* standard orientation, do nothing */ break; case GEXIV2_ORIENTATION_HFLIP: pika_image_flip (image, context, PIKA_ORIENTATION_HORIZONTAL, progress); break; case GEXIV2_ORIENTATION_ROT_180: pika_image_rotate (image, context, PIKA_ROTATE_180, progress); break; case GEXIV2_ORIENTATION_VFLIP: pika_image_flip (image, context, PIKA_ORIENTATION_VERTICAL, progress); break; case GEXIV2_ORIENTATION_ROT_90_HFLIP: /* flipped diagonally around '\' */ pika_image_rotate (image, context, PIKA_ROTATE_90, progress); pika_image_flip (image, context, PIKA_ORIENTATION_HORIZONTAL, progress); break; case GEXIV2_ORIENTATION_ROT_90: /* 90 CW */ pika_image_rotate (image, context, PIKA_ROTATE_90, progress); break; case GEXIV2_ORIENTATION_ROT_90_VFLIP: /* flipped diagonally around '/' */ pika_image_rotate (image, context, PIKA_ROTATE_90, progress); pika_image_flip (image, context, PIKA_ORIENTATION_VERTICAL, progress); break; case GEXIV2_ORIENTATION_ROT_270: /* 90 CCW */ pika_image_rotate (image, context, PIKA_ROTATE_270, progress); break; default: /* shouldn't happen */ break; } }