1 /* Xviewer -- Affine Transformations
2  *
3  * Copyright (C) 2003-2009 The Free Software Foundation
4  *
5  * Portions based on code from libart_lgpl by Raph Levien.
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 #include <time.h>
27 #include <stdlib.h>
28 #include <math.h>
29 #include <gtk/gtk.h>
30 #include <cairo/cairo.h>
31 
32 #include "xviewer-transform.h"
33 #include "xviewer-jobs.h"
34 
35 /* The number of progress updates per transformation */
36 #define XVIEWER_TRANSFORM_N_PROG_UPDATES 20
37 
38 struct _XviewerTransformPrivate {
39 	cairo_matrix_t affine;
40 };
41 
42 typedef struct {
43 	gdouble x;
44 	gdouble y;
45 } XviewerPoint;
46 
47 /* Convert degrees into radians */
48 #define XVIEWER_DEG_TO_RAD(degree) ((degree) * (G_PI/180.0))
49 
G_DEFINE_TYPE_WITH_PRIVATE(XviewerTransform,xviewer_transform,G_TYPE_OBJECT)50 G_DEFINE_TYPE_WITH_PRIVATE (XviewerTransform, xviewer_transform, G_TYPE_OBJECT)
51 
52 static void
53 xviewer_transform_init (XviewerTransform *trans)
54 {
55 	trans->priv = xviewer_transform_get_instance_private (trans);
56 }
57 
58 static void
xviewer_transform_class_init(XviewerTransformClass * klass)59 xviewer_transform_class_init (XviewerTransformClass *klass)
60 {
61 
62 }
63 
64 /**
65  * xviewer_transform_apply:
66  * @trans: a #XviewerTransform
67  * @pixbuf: a #GdkPixbuf
68  * @job: a #XviewerJob
69  *
70  * Applies the transformation in @trans to @pixbuf, setting its progress in @job.
71  *
72  * Returns: (transfer full): A new #GdkPixbuf with the transformation applied.
73  **/
74 GdkPixbuf*
xviewer_transform_apply(XviewerTransform * trans,GdkPixbuf * pixbuf,XviewerJob * job)75 xviewer_transform_apply (XviewerTransform *trans, GdkPixbuf *pixbuf, XviewerJob *job)
76 {
77 	XviewerPoint dest_top_left;
78 	XviewerPoint dest_bottom_right;
79 	XviewerPoint vertices[4] = { {0, 0}, {1, 0}, {1, 1}, {0, 1} };
80 	double r_det;
81 	int inverted [6];
82 	XviewerPoint dest;
83 
84 	int src_width;
85 	int src_height;
86 	int src_rowstride;
87 	int src_n_channels;
88 	guchar *src_buffer;
89 
90 	GdkPixbuf *dest_pixbuf;
91 	int dest_width;
92 	int dest_height;
93 	int dest_rowstride;
94 	int dest_n_channels;
95 	guchar *dest_buffer;
96 
97 	guchar *src_pos;
98 	guchar *dest_pos;
99 	int dx, dy, sx, sy;
100 	int i, x, y;
101 
102 	int progress_delta;
103 
104 	g_return_val_if_fail (pixbuf != NULL, NULL);
105 
106 	g_object_ref (pixbuf);
107 
108 	src_width = gdk_pixbuf_get_width (pixbuf);
109 	src_height = gdk_pixbuf_get_height (pixbuf);
110 	src_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
111 	src_n_channels = gdk_pixbuf_get_n_channels (pixbuf);
112 	src_buffer = gdk_pixbuf_get_pixels (pixbuf);
113 
114 	/* find out the dimension of the destination pixbuf */
115 	dest_top_left.x = 100000;
116 	dest_top_left.y = 100000;
117 	dest_bottom_right.x = -100000;
118 	dest_bottom_right.y = -100000;
119 
120 	for (i = 0; i < 4; i++) {
121 		dest.x = vertices[i].x * (src_width - 1);
122 		dest.y = vertices[i].y * (src_height -1);
123 
124 		cairo_matrix_transform_point (&trans->priv->affine,
125 					      &dest.x, &dest.y);
126 
127 		dest_top_left.x = MIN (dest_top_left.x, dest.x);
128 		dest_top_left.y = MIN (dest_top_left.y, dest.y);
129 
130 		dest_bottom_right.x = MAX (dest_bottom_right.x, dest.x);
131 		dest_bottom_right.y = MAX (dest_bottom_right.y, dest.y);
132 	}
133 
134 	/* create the resulting pixbuf */
135 	dest_width = abs (dest_bottom_right.x - dest_top_left.x + 1);
136 	dest_height = abs (dest_bottom_right.y - dest_top_left.y + 1);
137 
138 	dest_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
139 			       gdk_pixbuf_get_has_alpha (pixbuf),
140 			       gdk_pixbuf_get_bits_per_sample (pixbuf),
141 			       dest_width,
142 			       dest_height);
143 	dest_rowstride = gdk_pixbuf_get_rowstride (dest_pixbuf);
144 	dest_n_channels = gdk_pixbuf_get_n_channels (dest_pixbuf);
145 	dest_buffer = gdk_pixbuf_get_pixels (dest_pixbuf);
146 
147 	/* invert the matrix so that we can compute the source pixel
148 	   from the target pixel and convert the values to integer
149 	   ones (faster!)  FIXME: Maybe we can do some more
150 	   improvements by using special mmx/3dnow features if
151 	   available.
152 	*/
153 	r_det = 1.0 / (trans->priv->affine.xx * trans->priv->affine.yy - trans->priv->affine.yx * trans->priv->affine.xy);
154 	inverted[0] =  trans->priv->affine.yy * r_det;
155 	inverted[1] = -trans->priv->affine.yx * r_det;
156 	inverted[2] = -trans->priv->affine.xy * r_det;
157 	inverted[3] =  trans->priv->affine.xx * r_det;
158 	inverted[4] = -trans->priv->affine.x0 * inverted[0] - trans->priv->affine.y0 * inverted[2];
159 	inverted[5] = -trans->priv->affine.x0 * inverted[1] - trans->priv->affine.y0 * inverted[3];
160 
161 	progress_delta = MAX (1, dest_height / XVIEWER_TRANSFORM_N_PROG_UPDATES);
162 
163 	/*
164 	 * for every destination pixel (dx,dy) compute the source pixel (sx, sy)
165 	 * and copy the color values
166 	 */
167 	for (y = 0, dy = dest_top_left.y; y < dest_height; y++, dy++) {
168 		for (x = 0, dx = dest_top_left.x; x < dest_width; x++, dx++) {
169 
170 			sx = dx * inverted[0] + dy * inverted[2] + inverted[4];
171 			sy = dx * inverted[1] + dy * inverted[3] + inverted[5];
172 
173 			if (sx >= 0 && sx < src_width && sy >= 0 && sy < src_height) {
174 				src_pos  = src_buffer  + sy * src_rowstride  + sx * src_n_channels;
175 				dest_pos = dest_buffer +  y * dest_rowstride +  x * dest_n_channels;
176 
177 				for (i = 0; i <  src_n_channels; i++) {
178 					dest_pos[i] = src_pos[i];
179 				}
180 			}
181 		}
182 
183 		if (job != NULL && y % progress_delta == 0) {
184 			gfloat progress;
185 
186 			progress = (gfloat) (y + 1.0) / (gfloat) dest_height;
187 
188 			xviewer_job_set_progress (job, progress);
189 		}
190 	}
191 
192 	g_object_unref (pixbuf);
193 
194 	if (job != NULL) {
195 		xviewer_job_set_progress (job, 1.0);
196 	}
197 
198 	return dest_pixbuf;
199 }
200 
201 static void
_xviewer_cairo_matrix_copy(const cairo_matrix_t * src,cairo_matrix_t * dest)202 _xviewer_cairo_matrix_copy (const cairo_matrix_t *src, cairo_matrix_t *dest)
203 {
204 	cairo_matrix_init (dest, src->xx, src->yx, src->xy, src->yy, src->x0, src->y0);
205 }
206 
207 #define DOUBLE_EQUAL_MAX_DIFF 1e-6
208 #define DOUBLE_EQUAL(a,b) (fabs (a - b) < DOUBLE_EQUAL_MAX_DIFF)
209 /* art_affine_equal modified to work with cairo_matrix_t */
210 static gboolean
_xviewer_cairo_matrix_equal(const cairo_matrix_t * a,const cairo_matrix_t * b)211 _xviewer_cairo_matrix_equal (const cairo_matrix_t *a, const cairo_matrix_t *b)
212 {
213 	return (DOUBLE_EQUAL (a->xx, b->xx) && DOUBLE_EQUAL (a->yx, b->yx) &&
214 		DOUBLE_EQUAL (a->xy, b->xy) && DOUBLE_EQUAL (a->yy, b->yy) &&
215 		DOUBLE_EQUAL (a->x0, b->x0) && DOUBLE_EQUAL (a->y0, b->y0) );
216 }
217 
218 /* art_affine_flip modified to work with cairo_matrix_t */
219 static void
_xviewer_cairo_matrix_flip(cairo_matrix_t * dst,const cairo_matrix_t * src,gboolean horiz,gboolean vert)220 _xviewer_cairo_matrix_flip (cairo_matrix_t *dst, const cairo_matrix_t *src, gboolean horiz, gboolean vert)
221 {
222 	dst->xx = horiz ? -src->xx : src->xx;
223 	dst->yx = horiz ? -src->yx : src->yx;
224 	dst->xy = vert ? -src->xy : src->xy;
225 	dst->yy = vert ? -src->yy : src->yy;
226 	dst->x0 = horiz ? -src->x0 : src->x0;
227 	dst->y0 = vert ? -src->y0 : src->y0;
228 }
229 
230 /**
231  * xviewer_transform_reverse:
232  * @trans: a #XviewerTransform
233  *
234  * Creates the reverse transformation of @trans
235  *
236  * Returns: (transfer full): a new transformation
237  **/
238 XviewerTransform*
xviewer_transform_reverse(XviewerTransform * trans)239 xviewer_transform_reverse (XviewerTransform *trans)
240 {
241 	XviewerTransform *reverse;
242 
243 	g_return_val_if_fail (XVIEWER_IS_TRANSFORM (trans), NULL);
244 
245 	reverse = XVIEWER_TRANSFORM (g_object_new (XVIEWER_TYPE_TRANSFORM, NULL));
246 
247 	_xviewer_cairo_matrix_copy (&trans->priv->affine, &reverse->priv->affine);
248 
249 	g_return_val_if_fail (cairo_matrix_invert (&reverse->priv->affine) == CAIRO_STATUS_SUCCESS, reverse);
250 
251 	return reverse;
252 }
253 
254 /**
255  * xviewer_transform_compose:
256  * @trans: a #XviewerTransform
257  * @compose: another #XviewerTransform
258  *
259  *
260  *
261  * Returns: (transfer full): a new transform
262  **/
263 XviewerTransform*
xviewer_transform_compose(XviewerTransform * trans,XviewerTransform * compose)264 xviewer_transform_compose (XviewerTransform *trans, XviewerTransform *compose)
265 {
266 	XviewerTransform *composition;
267 
268 	g_return_val_if_fail (XVIEWER_IS_TRANSFORM (trans), NULL);
269 	g_return_val_if_fail (XVIEWER_IS_TRANSFORM (compose), NULL);
270 
271 	composition = XVIEWER_TRANSFORM (g_object_new (XVIEWER_TYPE_TRANSFORM, NULL));
272 
273 	cairo_matrix_multiply (&composition->priv->affine,
274 			       &trans->priv->affine,
275 			       &compose->priv->affine);
276 
277 	return composition;
278 }
279 
280 gboolean
xviewer_transform_is_identity(XviewerTransform * trans)281 xviewer_transform_is_identity (XviewerTransform *trans)
282 {
283 	static const cairo_matrix_t identity = { 1, 0, 0, 1, 0, 0 };
284 
285 	g_return_val_if_fail (XVIEWER_IS_TRANSFORM (trans), FALSE);
286 
287 	return _xviewer_cairo_matrix_equal (&identity, &trans->priv->affine);
288 }
289 
290 XviewerTransform*
xviewer_transform_identity_new(void)291 xviewer_transform_identity_new (void)
292 {
293 	XviewerTransform *trans;
294 
295 	trans = XVIEWER_TRANSFORM (g_object_new (XVIEWER_TYPE_TRANSFORM, NULL));
296 
297 	cairo_matrix_init_identity (&trans->priv->affine);
298 
299 	return trans;
300 }
301 
302 XviewerTransform*
xviewer_transform_rotate_new(int degree)303 xviewer_transform_rotate_new (int degree)
304 {
305 	XviewerTransform *trans;
306 
307 	trans = XVIEWER_TRANSFORM (g_object_new (XVIEWER_TYPE_TRANSFORM, NULL));
308 
309 	cairo_matrix_init_rotate (&trans->priv->affine, XVIEWER_DEG_TO_RAD(degree));
310 
311 	return trans;
312 }
313 
314 XviewerTransform*
xviewer_transform_flip_new(XviewerTransformType type)315 xviewer_transform_flip_new   (XviewerTransformType type)
316 {
317 	XviewerTransform *trans;
318 	gboolean horiz, vert;
319 
320 	trans = XVIEWER_TRANSFORM (g_object_new (XVIEWER_TYPE_TRANSFORM, NULL));
321 
322 	cairo_matrix_init_identity (&trans->priv->affine);
323 
324 	horiz = (type == XVIEWER_TRANSFORM_FLIP_HORIZONTAL);
325 	vert = (type == XVIEWER_TRANSFORM_FLIP_VERTICAL);
326 
327 	_xviewer_cairo_matrix_flip (&trans->priv->affine,
328 				&trans->priv->affine,
329 				horiz, vert);
330 
331 	return trans;
332 }
333 
334 XviewerTransform*
xviewer_transform_new(XviewerTransformType type)335 xviewer_transform_new (XviewerTransformType type)
336 {
337 	XviewerTransform *trans = NULL;
338 	XviewerTransform *temp1 = NULL, *temp2 = NULL;
339 
340 	switch (type) {
341 	case XVIEWER_TRANSFORM_NONE:
342 		trans = xviewer_transform_identity_new ();
343 		break;
344 	case XVIEWER_TRANSFORM_FLIP_HORIZONTAL:
345 		trans = xviewer_transform_flip_new (XVIEWER_TRANSFORM_FLIP_HORIZONTAL);
346 		break;
347 	case XVIEWER_TRANSFORM_ROT_180:
348 		trans = xviewer_transform_rotate_new (180);
349 		break;
350 	case XVIEWER_TRANSFORM_FLIP_VERTICAL:
351 		trans = xviewer_transform_flip_new (XVIEWER_TRANSFORM_FLIP_VERTICAL);
352 		break;
353 	case XVIEWER_TRANSFORM_TRANSPOSE:
354 		temp1 = xviewer_transform_rotate_new (90);
355 		temp2 = xviewer_transform_flip_new (XVIEWER_TRANSFORM_FLIP_HORIZONTAL);
356 		trans = xviewer_transform_compose (temp1, temp2);
357 		g_object_unref (temp1);
358 		g_object_unref (temp2);
359 		break;
360 	case XVIEWER_TRANSFORM_ROT_90:
361 		trans = xviewer_transform_rotate_new (90);
362 		break;
363 	case XVIEWER_TRANSFORM_TRANSVERSE:
364 		temp1 = xviewer_transform_rotate_new (90);
365 		temp2 = xviewer_transform_flip_new (XVIEWER_TRANSFORM_FLIP_VERTICAL);
366 		trans = xviewer_transform_compose (temp1, temp2);
367 		g_object_unref (temp1);
368 		g_object_unref (temp2);
369 		break;
370 	case XVIEWER_TRANSFORM_ROT_270:
371 		trans = xviewer_transform_rotate_new (270);
372 		break;
373 	default:
374 		trans = xviewer_transform_identity_new ();
375 		break;
376 	}
377 
378 	return trans;
379 }
380 
381 XviewerTransformType
xviewer_transform_get_transform_type(XviewerTransform * trans)382 xviewer_transform_get_transform_type (XviewerTransform *trans)
383 {
384 	cairo_matrix_t affine, a1, a2;
385 	XviewerTransformPrivate *priv;
386 
387 	g_return_val_if_fail (XVIEWER_IS_TRANSFORM (trans), XVIEWER_TRANSFORM_NONE);
388 
389 	priv = trans->priv;
390 
391 	cairo_matrix_init_rotate (&affine, XVIEWER_DEG_TO_RAD(90));
392 	if (_xviewer_cairo_matrix_equal (&affine, &priv->affine)) {
393 		return XVIEWER_TRANSFORM_ROT_90;
394 	}
395 
396 	cairo_matrix_init_rotate (&affine, XVIEWER_DEG_TO_RAD(180));
397 	if (_xviewer_cairo_matrix_equal (&affine, &priv->affine)) {
398 		return XVIEWER_TRANSFORM_ROT_180;
399 	}
400 
401 	cairo_matrix_init_rotate (&affine, XVIEWER_DEG_TO_RAD(270));
402 	if (_xviewer_cairo_matrix_equal (&affine, &priv->affine)) {
403 		return XVIEWER_TRANSFORM_ROT_270;
404 	}
405 
406 	cairo_matrix_init_identity (&affine);
407 	_xviewer_cairo_matrix_flip (&affine, &affine, TRUE, FALSE);
408 	if (_xviewer_cairo_matrix_equal (&affine, &priv->affine)) {
409 		return XVIEWER_TRANSFORM_FLIP_HORIZONTAL;
410 	}
411 
412 	cairo_matrix_init_identity (&affine);
413 	_xviewer_cairo_matrix_flip (&affine, &affine, FALSE, TRUE);
414 	if (_xviewer_cairo_matrix_equal (&affine, &priv->affine)) {
415 		return XVIEWER_TRANSFORM_FLIP_VERTICAL;
416 	}
417 
418 	cairo_matrix_init_rotate (&a1, XVIEWER_DEG_TO_RAD(90));
419 	cairo_matrix_init_identity (&a2);
420 	_xviewer_cairo_matrix_flip (&a2, &a2, TRUE, FALSE);
421 	cairo_matrix_multiply(&affine, &a1, &a2);
422 	if (_xviewer_cairo_matrix_equal (&affine, &priv->affine)) {
423 		return XVIEWER_TRANSFORM_TRANSPOSE;
424 	}
425 
426 	/* A transversion is a 180° rotation followed by a transposition */
427 	/* Reuse the transposition from the previous step for this. */
428 	cairo_matrix_init_rotate (&a1, XVIEWER_DEG_TO_RAD(180));
429 	cairo_matrix_multiply(&a2, &a1, &affine);
430 	if (_xviewer_cairo_matrix_equal (&a2, &priv->affine)) {
431 		return XVIEWER_TRANSFORM_TRANSVERSE;
432 	}
433 
434 	return XVIEWER_TRANSFORM_NONE;
435 }
436 
437 gboolean
xviewer_transform_get_affine(XviewerTransform * trans,cairo_matrix_t * affine)438 xviewer_transform_get_affine (XviewerTransform *trans, cairo_matrix_t *affine)
439 {
440 	g_return_val_if_fail (XVIEWER_IS_TRANSFORM (trans), FALSE);
441 
442 	_xviewer_cairo_matrix_copy (&trans->priv->affine, affine);
443 
444 	return TRUE;
445 }
446 
447