Custom image class to transparently rotate an image

252 views
Skip to first unread message

Elby 64

unread,
Dec 5, 2021, 8:15:31 AM12/5/21
to kivy-...@googlegroups.com
    Hi,

I'm trying to make a custom image class to be able to rotate an image in a transparent way.
My aim is to make a class to automatically rotate an image according to its exif data.

I've found some examples on the internet to use canvas.before and canvas.after to rotate the image.
But the position and the size of the image are not correct.

My idea was to use an internal Image widget (named img_i) and change its size according to the rotation angle.
If the image is transposed (i.e. rotated by 90 or 270 degrees) a rotated image of
size (w,h) would need an internal image of size (h, w) before rotation.

Here is the code I've tried :

[code]
# -*- coding: utf-8 -*-

import os
import sys
import pickle

# use PIL to load images by default
os.environ['KIVY_IMAGE'] = 'pil,sdl2'

import kivy
from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import NumericProperty, ObjectProperty, StringProperty, ListProperty, BooleanProperty

from kivy.lang import Builder


Builder.load_string("""
<RotatedImage@FloatLayout>:
    img_i : image_id
    transposed:  (root.angle == 90) or (root.angle == 270)

    canvas.before:
        PushMatrix
        Rotate:
            angle: root.angle
            axis: 0, 0, 1
            origin: root.center
    canvas.after:
        PopMatrix

    Image:
        id : image_id
        keep_ratio: True
        allow_stretch: True
""")

class RotatedImage(FloatLayout):
    img_i = ObjectProperty(None)
    angle = NumericProperty(0.0)
    transposed = BooleanProperty(False)

    def __init__(self, *args, **kwds):
        super(RotatedImage, self).__init__(*args, **kwds)
        self.bind(size=self.update_size,
                 transposed=self.update_size)

    def update_size(self, *args, **kwds):
        print('update_size :')
        print(f'  root: pos={self.pos} size={self.size} transposed={self.transposed}')
        if self.transposed :
            w, h = self.size
            self.img_i.size = h, w
            self.img_i.center = self.center
        else :
            self.img_i.size = self.size
            self.img_i.center = self.center

        print(f'  img : pos={self.img_i.pos} size={self.img_i.size}')
        print(f'  root: pos={self.pos} size={self.size} transposed={self.transposed}')

    def set_image(self, path, angle=0.0):
        self.img_i.source = path
        self.angle = angle
        self.update_size()


class KvTestApp(App):

    def build(self):
        self.root =  RotatedImage()
        return self.root

    def on_start(self, *args):
        path = os.path.realpath(os.path.join('.', 'images', 'right.jpg'))
        self.root.set_image(path, angle=270)

if __name__ == '__main__':
    app = KvTestApp()
    app.run()

[/code]

The image is rotated as I wanted but its position and scale are wrong.

It seems that updating the size and the center of the internal image in up_date_size method was not enough to have a correct display.

Have you got any clue to make it work, or any better idea to rotate this image ?

Thanks for your help.

Regards,

    Elby.

Elliot Garbus

unread,
Dec 5, 2021, 11:29:17 AM12/5/21
to kivy-...@googlegroups.com

Just taking a quick look at your code, you are missing a few calculations.

Just like you are swapping height and width, you need to swap center_x and center_y. (self.center[0]. self.center[1])

You may also need to adjust the pos.

I suspect you want your RotateImage to be a Widget, not a FloatLayout. 

 

I’d suggest deriving the RotatedImage from an Image widget and rotating the canvas.

--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/CAG2bR8x0z2sOeNBV-7i9v7D4NSOKr%3DBVd9iGQ%2B8s-XshSD1jyA%40mail.gmail.com.

 

Elliot Garbus

unread,
Dec 5, 2021, 12:22:10 PM12/5/21
to kivy-...@googlegroups.com

Giving it a try myself, I think the code below does what you are looking for.  Note you will need to change the name of the image file in the kv code.

 

import itertools

from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import NumericProperty, ListProperty, BooleanProperty
from kivy.uix.image import Image

kv =
"""
<RotatedImage>:
    keep_ratio: True
    allow_stretch: True

    transposed:  (root.angle == 90) or (root.angle == 270)
    canvas.before:
        PushMatrix
        Rotate:
            angle: root.angle
            axis: 0, 0, 1
            origin: root.center
    canvas.after:
        PopMatrix
BoxLayout:
    Label:
        text: 'Image Rotate Test'
    RotatedImage:
        id: ri
        source: 'ACESxp-30230 crop.jpg'  # CHANGE TO YOUR IMAGE FILE
    Label:
        text: 'Image Rotate Test'
       
"""

class RotatedImage(Image):
    angle = NumericProperty(
0.0)
    transposed = BooleanProperty(
False)

   
def set_image(self, path, angle=0.0):
       
self.source = path
       
self.angle = angle


class KvTestApp(App):
    angles = itertools.cycle([
0, 90, 270])

   
def build(self):
       
return Builder.load_string(kv)

   
def on_start(self, *args):
        Clock.schedule_interval(
self.rotate, 2)

   
def rotate(self, *args):
       
self.root.ids.ri.angle = next(self.angles)


Elliot Garbus

unread,
Dec 5, 2021, 12:37:13 PM12/5/21
to kivy-...@googlegroups.com

A small edit to make things more ‘pythonic’

transposed:  root.angle in (90, 270)

 

 

 

From: Elliot Garbus
Sent: Sunday, December 5, 2021 10:22 AM
To: kivy-...@googlegroups.com
Subject: RE: [kivy-users] Custom image class to transparently rotate animage

 

Giving it a try myself, I think the code below does what you are looking for.  Note you will need to change the name of the image file in the kv code.

 

import itertools

from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import NumericProperty, ListProperty, BooleanProperty
from kivy.uix.image import Image

kv =
"""

<RotatedImage>:
    keep_ratio: True
    allow_stretch: True
    transposed:  (root.angle == 90) or (root.angle == 270)
    canvas.before:
        PushMatrix
        Rotate:
            angle: root.angle
            axis: 0, 0, 1
            origin: root.center
    canvas.after:
        PopMatrix
BoxLayout:
    Label:
        text: 'Image Rotate Test'
    RotatedImage:
        id: ri
        source: 'ACESxp-30230 crop.jpg'  # CHANGE TO YOUR IMAGE FILE
    Label:
        text: 'Image Rotate Test'
       
"""

class RotatedImage(Image):
    angle = NumericProperty(
0.0)
    transposed = BooleanProperty(
False)

   
def set_image(self, path, angle=0.0):
       
self.source = path
       
self.angle = angle


class KvTestApp(App):
    angles = itertools.cycle([
0, 90, 270])

   
def build(self):
       
return Builder.load_string(kv)

   
def on_start(self, *args):
        Clock.schedule_interval(
self.rotate, 2)

   
def rotate(self, *args):
       
self.root.ids.ri.angle = next(self.angles)


if __name__ == '__main__':
    app = KvTestApp()
    app.run()

 

 

 

 


Sent: Sunday, December 5, 2021 9:29 AM
To: kivy-...@googlegroups.com

Elby

unread,
Dec 6, 2021, 12:38:26 PM12/6/21
to Kivy users support
Thanks for your reply.

It's not exactly was I was looking for.
This solution rotate effectively the image but the result is not the same as if
I had rotated the image outside Kivy: the allow_stretch and keep_ratio option
are not transparently taken into account.

Let me clarify a bit what I mean with an example.
Let's assume:
- the layout containing the image has a size (wl, hl) = (800, 600).
- the original image has a size (wi x hi) = (400 x 200) pixels

With allow_stretch and keep_ratio activated, Kivy will calculate the higher
factor to apply to the image so it stay in the layout. This give a factor
equals to wl/wi=2.
The image is displayed as if its size was (800x400).

If I rotate the image outside of Kivy, i will get an image of size (200 x 400)
Allow_strech and keep_ratio gives a factor equals to 1.5 and the image is drawn
as if its size was (300 x 600).

With the solution you presented, the image will be drawn as a (400 x 800) image and some parts are truncated.

What the best way to take into account the resizing made by the allow_stretch and keep_ratio options ?

Elliot Garbus

unread,
Dec 6, 2021, 8:08:36 PM12/6/21
to kivy-...@googlegroups.com

I don’t understand what you are trying to achieve.   Happy to help if you can help me understand.

You can of course set allow_stretch: False

 

The Image property norm_image_size provides the size of the scaled image.

The image property texture size provides the size of the original image.

 

https://kivy.org/doc/master/api-kivy.uix.image.html?highlight=image#kivy.uix.image.Image.norm_image_size

https://kivy.org/doc/master/api-kivy.uix.image.html?highlight=image#kivy.uix.image.Image.texture_size

Kirk Jackson

unread,
Dec 6, 2021, 9:53:05 PM12/6/21
to kivy-...@googlegroups.com
Dear Mr. Garbus:

Each picture is a Scatter object containing a picture with FloatLayout as parent.  The picture bind to the float by x,y coordinates.

Now, please understand that I am binding multiple images on the canvas like a contact sheet of photos and creating unlimited aesthetic layouts like diagnals and so on.  So, given this scenario I think I'm ok.

Understanding multiple images on a canvas, if you would like to branch and commit then that would be wonderful.

How would I go about multiple JSON settings configurations.  This is important because each photo layout is dependent on the settings.

You might try running the program on a photo directory with lots of photos to see the magic.  The photos load asynchronously.

Again, thanks so much for your kind advice.

Sincerely,


Kirk A Jackson, B.S., Investor
Secure Investments
4015 Executive Park Drive
Suite 338
Sharonville, Ohio  45241
Cell:  (513) 266-4954
jackso...@gmail.com


Kirk Jackson

unread,
Dec 6, 2021, 10:08:07 PM12/6/21
to kivy-...@googlegroups.com
Dear Mr. Garbus:

Finally, I found the culprit.  It is the class SettingsWithSidebar.  This class causes the error.  When I used SettingsWithTabbedPanel or SettingsWithSpinner I don't get the error.

Sincerely,


Kirk A Jackson, B.S., Investor
Secure Investments
4015 Executive Park Drive
Suite 338
Sharonville, Ohio  45241
Cell:  (513) 266-4954
jackso...@gmail.com

Elby

unread,
Dec 7, 2021, 2:15:47 AM12/7/21
to Kivy users support
Mea culpa: I've read all your answers but I had tested only the latest one.
It seems that the information I needed was in your first answer.

If the RotatedImage class in my first example inherits from Widget instead of FloatLayout I get something very close to what I'm looking for:
the rotated image is resized transparently to fit the widget size according to the allow_stretch and keep_ratio options.

Thanks a lot.
Reply all
Reply to author
Forward
0 new messages