From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED.blaine.gmane.org!not-for-mail From: Alan Third Newsgroups: gmane.emacs.devel Subject: Re: Image transformations Date: Sun, 16 Jun 2019 16:22:59 +0100 Message-ID: <20190616152259.GA22789@breton.holly.idiocy.org> References: <20190612220746.GA89208@breton.holly.idiocy.org> <834l4u11dr.fsf@gnu.org> <20190613165804.GB11266@breton.holly.idiocy.org> <83d0jhz9za.fsf@gnu.org> <20190613192724.GA11945@breton.holly.idiocy.org> <83zhmlxo6d.fsf@gnu.org> <20190613222626.GA12971@breton.holly.idiocy.org> <83o930y7cl.fsf@gnu.org> <20190615104242.GA13368@breton.holly.idiocy.org> <838su3w0de.fsf@gnu.org> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="45Z9DzgjV8m4Oswq" Content-Transfer-Encoding: 8bit Injection-Info: blaine.gmane.org; posting-host="blaine.gmane.org:195.159.176.226"; logging-data="228487"; mail-complaints-to="usenet@blaine.gmane.org" User-Agent: Mutt/1.12.0 (2019-05-25) Cc: emacs-devel@gnu.org To: Eli Zaretskii Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sun Jun 16 17:23:19 2019 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([209.51.188.17]) by blaine.gmane.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.89) (envelope-from ) id 1hcX03-000xF1-7Z for ged-emacs-devel@m.gmane.org; Sun, 16 Jun 2019 17:23:19 +0200 Original-Received: from localhost ([::1]:40784 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.86_2) (envelope-from ) id 1hcX02-0000hi-7h for ged-emacs-devel@m.gmane.org; Sun, 16 Jun 2019 11:23:18 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:49387) by lists.gnu.org with esmtp (Exim 4.86_2) (envelope-from ) id 1hcWzu-0000hb-DX for emacs-devel@gnu.org; Sun, 16 Jun 2019 11:23:12 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hcWzs-0007jX-1p for emacs-devel@gnu.org; Sun, 16 Jun 2019 11:23:10 -0400 Original-Received: from mail-wm1-x32d.google.com ([2a00:1450:4864:20::32d]:53922) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1hcWzo-0007e4-QG; Sun, 16 Jun 2019 11:23:05 -0400 Original-Received: by mail-wm1-x32d.google.com with SMTP id x15so6742031wmj.3; Sun, 16 Jun 2019 08:23:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlemail.com; s=20161025; h=sender:date:from:to:cc:subject:message-id:references:mime-version :content-disposition:content-transfer-encoding:in-reply-to :user-agent; bh=t8OkOC22CswQGoUpBbkFq+r6ITJZEFdjsI4tL2yzuP0=; b=ju4IQGHSQnbyT+4x3qnZoX85nwSrykHD9KYJ8nwZLH6OdZEN7oncwXzDXDaSoee9B0 kNO7KHFHtOBbasU5tGQTGqR2ffv6tpDEKajWMNd1ce1XfSjv4sRYxVwOgOJIht9JL63W 8fulSoSKOo4qw92yRKqcN+5h4ff2izQ+nJRh7op734l7jbAqcIRZAog3o1LaSLpFwDKB 9rd5Fepd7Wa5pE3yl0TmW1eybJ9np6J/eNCAAGcEeYhROSVInQye7wFN6BRUq6ydZDlz NeHFN8sprRlfB9MlD6dcailscsog0/jgm2R54bMCXeadImxR3YE02oWCZGoN+SAmxMzF aXUA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:date:from:to:cc:subject:message-id :references:mime-version:content-disposition :content-transfer-encoding:in-reply-to:user-agent; bh=t8OkOC22CswQGoUpBbkFq+r6ITJZEFdjsI4tL2yzuP0=; b=BQBW1DrES7IHuxCECeYWDiwuSzDxDdkAa47oOSH2+r99wFx8NyWtcymbuBX7lxi2kq yLJbNRch0Batdd6I73t56U5iTTSnLh/sv9NaSRAdBVU+RJawPU3MTIhwdLeJA6QDT4fI qiHvT652tThYz1PalTg0X4gAOV1Qrl9JptR4HVMSQIIUqDW89dhjaniZkuAzscMXyi77 19FI9MmUOokvWNbEa3ih786c/l/fvkxNU+8QZjEurtWY6HdFd6rVc83o8owx7/mfkVbE 9xgcFc1zVXTKjS9wxNXJthqMnjuoXM0uCyrwo6PxqbprNR473OZa5ggmNBCW854HdIR3 vPBw== X-Gm-Message-State: APjAAAUxouD+PhUohTJ9s7pVBXkVl1Dpn9KaoB54V6qsNuh5Ivv5R0H3 qQpqUBqEJPFpOpgZZ9Vo6weKysff X-Google-Smtp-Source: APXvYqzDI+nNvBYIwDJwOdCOD5GfYJha8qlqMt4PAaGXdlo2kQ5196o97iNqVPWJlGy/QRasX6bGDQ== X-Received: by 2002:a1c:f515:: with SMTP id t21mr16301890wmh.39.1560698582831; Sun, 16 Jun 2019 08:23:02 -0700 (PDT) Original-Received: from breton.holly.idiocy.org (ip6-2001-08b0-03f8-8129-706b-438e-df0c-a138.holly.idiocy.org. [2001:8b0:3f8:8129:706b:438e:df0c:a138]) by smtp.gmail.com with ESMTPSA id r2sm9101036wme.30.2019.06.16.08.23.01 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 16 Jun 2019 08:23:01 -0700 (PDT) Content-Disposition: inline In-Reply-To: <838su3w0de.fsf@gnu.org> X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 2a00:1450:4864:20::32d X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.org gmane.emacs.devel:237730 Archived-At: --45Z9DzgjV8m4Oswq Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit On Sat, Jun 15, 2019 at 02:31:41PM +0300, Eli Zaretskii wrote: > > Date: Sat, 15 Jun 2019 11:42:42 +0100 > > From: Alan Third > > Cc: emacs-devel@gnu.org > > > > > The matrix is defined according to XRender, AFAIU. NS and Cairo need > > > to transpose/invert/etc. the matrix to use it, and so will the > > > MS-Windows code, IIUC. The matrix fuses the actual primitive > > > transformations into a construct which we currently don't seem to > > > understand well, whose only "documentation" is in a tutorial that is > > > not part of XRender's official docs. > > > > I was first taught affine transformation matrices in a Mathematics for > > Engineers course at university. > > We are mis-communicating. I know what affine transformations are, and > I have no problems with matrix multiplication and vector algebra in > general. I majored in physics, so even tensors of General Relativity > aren't a problem for me. I know nothing of your background other than what I’ve read on here, so I apologise for being patronising. I’ve clearly misunderstood what you were asking for repeatedly. > The issue I'm worried about is the geometrical meaning of each element > of the matrix, as applied to image transformations in Emacs: whether > it needs to be transposed before multiplying vectors by it, whether > vectors should be left-multiplied or right-multiplied, whether the Y > axis is assumed to go up or down, whether the [1][2] member is the > sine of the rotation angle or its negative, etc. > > Even Richard and Alp were confused for a moment about this stuff. > Which is a clear sign that these aspects are not as trivial as it > might sound. IMO, we need clear documentation of that, to allow > people make changes in the code without making mistakes. But maybe > I'm the only one to be bothered by that. I think as long as we’re consistent with our own representation then we can treat the question of whether the toolkits have transposed the matrix terms as implementation details. The code IS consistent, unfortunately my first attempt at documenting it WASN’T, which is entirely on me. That caused at least some of the confusion. The attached patch has more, hopefully better, documentation. > > BTW, are you really keen to get rid of the matrix multiplications? > > Anything we replace them with for XRender will simply be matrix > > multiplications written out in long form, keeping track of each > > element separately. > > This is again a misunderstanding: I was merely suggesting to make all > of those multiplications in image_set_transform, that's all. OK. Do you want me to go ahead and do that? I don’t want to start making big changes if you’re working on the Windows side. I have a suggestion that will simplify this job though. In another part of this thread you pointed out that crop and image slices are doing basically the same thing. I hadn’t realised that. Given that that’s the case, I think we should just get rid of the crop function. Getting rid of it will also get rid of that difficult to understand resize ‐> crop ‐> rotate process where crop parameters have to take the effects of the resize into account. > Maybe I should stop talking about this and just go ahead and write the > best code I can. Because instead of improving things, this discussion > seems to just proliferate misunderstandings and bad feelings. Most > probably, my fault, sorry. No bad feelings on my part, just confusion, which was largely my own fault anyway. I hope we can finish this off without any more misunderstandings. -- Alan Third --45Z9DzgjV8m4Oswq Content-Type: text/plain; charset=utf-8 Content-Disposition: attachment; filename="0001-Document-image-transforms.patch" Content-Transfer-Encoding: 8bit >From bcd6c010f5fe78d4254d9c5e8181d37415f9744c Mon Sep 17 00:00:00 2001 From: Alan Third Date: Tue, 11 Jun 2019 20:31:24 +0100 Subject: [PATCH] Document image transforms * doc/lispref/display.texi (Image Descriptors): Document :crop and update :rotation. * src/image.c: Describe the image transform matrix layout. * test/manual/image-transforms-tests.el: New file. --- doc/lispref/display.texi | 23 +++- src/image.c | 80 ++++++++++++ test/manual/image-transforms-tests.el | 176 ++++++++++++++++++++++++++ 3 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 test/manual/image-transforms-tests.el diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 93c5217c36..5a5b77e709 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -5181,8 +5181,29 @@ Image Descriptors specified, the height/width will be adjusted by the specified scaling factor. +@item :crop @var{geometry} +This should be a list of the form @code{(@var{width} @var{height} +@var{x} @var{y})}. @var{width} and @var{height} specify the width +and height of the cropped image. If @var{x} is a positive number it +specifies the offset of the cropped area from the left of the original +image, and if negative the offset from the right. If @var{y} is a +positive number it specifies the offset from the top of the original +image, and if negative from the bottom. If @var{x} or @var{y} are +@code{nil} or unspecified the crop area will be centred on the +original image. + +If the crop area is outside or overlaps the edge of the image it will +be reduced to exclude any areas outside of the image. This means it +is not possible to use @code{:crop} to increase the size of the image +by entering large @var{width} or @var{height} values. + +Cropping is performed after scaling but before rotation. + @item :rotation @var{angle} -Specifies a rotation angle in degrees. +Specifies a rotation angle in degrees. Only multiples of 90 degrees +are supported, unless the image type is @code{imagemagick}. Positive +values rotate clockwise, negative values counter-clockwise. Rotation +is performed after scaling and cropping. @item :index @var{frame} @xref{Multi-Frame Images}. diff --git a/src/image.c b/src/image.c index 86f8e8f4bb..6dc14db880 100644 --- a/src/image.c +++ b/src/image.c @@ -1967,6 +1967,86 @@ compute_image_size (size_t width, size_t height, } #endif /* HAVE_IMAGEMAGICK || HAVE_NATIVE_TRANSFORMS */ +/* image_set_rotation, image_set_crop, image_set_size and + image_set_transform use affine transformation matrices to perform + various transforms on the image. The matrix is a 2D array of + doubles. It is laid out like this: + + m[0][0] = m11 | m[1][0] = m12 | m[2][0] = tx + --------------+---------------+------------- + m[0][1] = m21 | m[1][1] = m22 | m[2][1] = ty + --------------+---------------+------------- + m[0][2] = 0 | m[1][2] = 0 | m[2][2] = 1 + + tx and ty represent translations, m11 and m22 represent scaling + transforms and m21 and m12 represent shear transforms. Most + graphics toolkits don't require the third row, however it is + necessary for multiplication. + + Transforms are done by creating a matrix for each action we wish to + take, then multiplying the transformation matrix by each of those + matrices in order (matrix multiplication is not commutative). + After we’ve done that we can use our modified transformation matrix + to transform points. We take the x and y coordinates and convert + them into a 3x1 matrix and multiply that by the transformation + matrix and it gives us a new set of coordinates: + + [x] [m11 m12 tx] [x'] + [y] X [m21 m22 ty] = [y'] + [1] [ 0 0 1] [1 ] + + Luckily we don’t have to worry about the last step as the graphics + toolkit will do it for us. + + The three transforms we are concerned with are translation, scaling + and rotation. The translation matrix looks like this: + + [1 0 tx] + [0 1 ty] + [0 0 1] + + Where tx and ty are the amount to translate the origin in the x and + y coordinates, respectively. Since we are translating the origin + and not the image data itself, it can appear backwards in use, for + example to move the image 10 pixels to the right, you would set tx + to -10. + + To scale we use: + + [x 0 0] + [0 y 0] + [0 0 1] + + Where x and y are the amounts to scale in the x and y dimensions. + Values larger than 1 make the image larger, values smaller than 1 + make it smaller. Negative values flip the image. + + To rotate we use: + + [ cos(r) sin(r) 0] + [-sin(r) cos(r) 0] + [ 0 0 1] + + Where r is the angle of rotation required. Rotation occurs around + the origin, not the centre of the image. Note that mathematically + this is considered a counter-clockwise rotation, however because + our y axis is reversed, (0, 0) at the top left, it works as a + clockwise rotation. + + The full process of rotating an image is to move the origin to the + centre of the image (width/2, height/2), perform the rotation, and + finally move the origin back to the top left of the image, which + may now be a different corner. + + Cropping is easier as we just move the origin to the top left of + where we want to crop and set the width and height accordingly. + The matrices don’t know anything about width and height. + + It's possible to pre-calculate the matrix multiplications and just + generate one transform matrix that will do everything we need in a + single step, but the maths for each element is much more complex + and I thought it was better to perform the steps separately. */ + typedef double matrix3x3[3][3]; static void diff --git a/test/manual/image-transforms-tests.el b/test/manual/image-transforms-tests.el new file mode 100644 index 0000000000..d601b9397e --- /dev/null +++ b/test/manual/image-transforms-tests.el @@ -0,0 +1,176 @@ +;;; image-transform-tests.el --- Test suite for image transforms. + +;; Copyright (C) 2019 Free Software Foundation, Inc. + +;; Author: Alan Third +;; Keywords: internal +;; Human-Keywords: internal + +;; This file is part of GNU Emacs. + +;; GNU Emacs 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. + +;; GNU Emacs 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 GNU Emacs. If not, see . + +;;; Commentary: + +;; Type M-x test-transforms RET to generate the test buffer. + +;;; Code: + +(defun test-rotation () + (let ((up "") + (down "") + (left "") + (right "")) + (insert-header "Test Rotation: rotating an image") + (insert-test "0" up up '(:rotation 0)) + (insert-test "360" up up '(:rotation 360)) + (insert-test "180" down up '(:rotation 180)) + (insert-test "-90" left up '(:rotation -90)) + (insert-test "90" right up '(:rotation 90)) + (insert-test "90.0" right up '(:rotation 90.0)) + + ;; This should log a message and display the unrotated image. + (insert-test "45" up up '(:rotation 45))) + (insert "\n\n")) + +(defun test-cropping () + (let ((image " + + + + + + ") + (top-left " + + ") + (middle " + + + + ") + (bottom-right " + + ")) + (insert-header "Test Crop: cropping an image") + (insert-test "all params" top-left image '(:crop (10 10 0 0))) + (insert-test "width/height only" middle image '(:crop (10 10))) + (insert-test "negative x y" middle image '(:crop (10 10 -10 -10))) + (insert-test "all params" bottom-right image '(:crop (10 10 20 20)))) + (insert "\n\n")) + +(defun test-scaling () + (let ((image " + + + + ") + (large " + + + + ") + (small " + + + + ")) + (insert-header "Test Scaling: resize an image (pixelization may occur)") + (insert-test "1x" image image '(:scale 1)) + (insert-test "2x" large image '(:scale 2)) + (insert-test "0.5x" image large '(:scale 0.5)) + (insert-test ":max-width" image large '(:max-width 10)) + (insert-test ":max-height" image large '(:max-height 10)) + (insert-test "width, height" image large '(:width 10 :height 10))) + (insert "\n\n")) + +(defun test-scaling-rotation () + (let ((image " + + + ") + (x2-90 " + + + ") + (x2--90 " + + + ") + (x0.5-180 " + + + ")) + (insert-header "Test Scaling and Rotation: resize and rotate an image (pixelization may occur)") + (insert-test "1x, 0 degrees" image image '(:scale 1 :rotation 0)) + (insert-test "2x, 90 degrees" x2-90 image '(:scale 2 :rotation 90.0)) + (insert-test "2x, -90 degrees" x2--90 image '(:scale 2 :rotation -90.0)) + (insert-test "0.5x, 180 degrees" x0.5-180 image '(:scale 0.5 :rotation 180.0))) + (insert "\n\n")) + +(defun insert-header (description) + (insert description) + (insert "\n") + (indent-to 38) + (insert "expected") + (indent-to 48) + (insert "result") + (when (fboundp #'imagemagick-types) + (indent-to 58) + (insert "ImageMagick")) + (insert "\n")) + +(defun insert-test (description expected image params) + (indent-to 2) + (insert description) + (indent-to 40) + (insert-image (create-image expected 'svg t)) + (indent-to 50) + (insert-image (apply #'create-image image 'svg t params)) + (when (fboundp #'imagemagick-types) + (indent-to 60) + (insert-image (apply #'create-image image 'imagemagick t params))) + (insert "\n")) + +(defun test-transforms () + (interactive) + (let ((buf (get-buffer "*Image Transform Test*"))) + (if buf + (kill-buffer buf)) + (switch-to-buffer (get-buffer-create "*Image Transform Test*")) + (erase-buffer) + (unless #'imagemagick-types + (insert "ImageMagick not detected. ImageMagick tests will be skipped.\n\n")) + (test-rotation) + (test-cropping) + (test-scaling) + (test-scaling-rotation) + (goto-char (point-min)))) -- 2.21.0 --45Z9DzgjV8m4Oswq--