Deprecated: Return type of I::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 62

Deprecated: Return type of I::next() should either be compatible with Iterator::next(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 91

Deprecated: Return type of I::key() should either be compatible with Iterator::key(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 71

Deprecated: Return type of I::valid() should either be compatible with Iterator::valid(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 101

Deprecated: Return type of I::rewind() should either be compatible with Iterator::rewind(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 53

Deprecated: Return type of Collection::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/collection.php on line 80

Deprecated: parse_str(): Passing null to parameter #1 ($string) of type string is deprecated in /home/public/kirby/toolkit/lib/url.php on line 135
One Tap Less | Cropping Images in Editorial

Cropping Images in Editorial

When Ole Moritz released Editorial 1.1, the geeks out there freaked out. Not only was Editorial an universal app now, it also added a bunch of delicacies for our delight. One of them is the ui module to create custom interfaces. Viticci illustrated his review on the update with a couple examples1 and I decided to give it a go after reading the documentation.

I always missed a decent and responsive tool to crop images on iOS, so I pinned that on my wall and made it my goal. My general idea was to prompt the user for a Camera Roll picture and display it on full screen, then he would be able to touch and drag to select the area he wants to crop, after that, he could tap the Crop to grab the result or Clear to remove the selection.

The first steps, such as displaying the image, were easy, but I was aware that I'd need Custom Views to handle the touch events and Python classes terrify me, I admit. Creating an UI is all about pilling Views and I managed to make the view responsible for displaying the image (ImageView from now on) to be fit the screen in different locations, yet, the invisible view where touch events happen (DragView from now on) had to be the exact same size as the resized ImageView and that's a hole Ole dragged me out with his advice.

From that moment on things flew, I created a discreet interface to display the selection that would dynamically respond to touch moves. Probably the hardest thing was to actually crop the images. The image you have displayed is probably smaller then the one you selected, so when you crop the image, I wanted to maintain the dimensions of the original picture, so I had to convert the selection dimensions and positions to the exact same outcome in the original image. And we haven't even cropped the image yet.

After that, I failed several times trying to crop the image. I was aware it was a drawing context where I had to create anImageContext, the docs hold some good examples of those using paths, not images and there is no crop tool in the Image functions, so I had to improvise. The first idea was to use clip_to_mask to get our cropped image, however, the outcome would not change the position, so, for example, considering a full screen ImageContext, you'd get the cropped image and all the space left in blank.

My solution was to create a secondary ImageContext to deal with the clipping only and then use Transform.translation to place the outcome in the position (0,0). The whole thing is probably clunky, but it works.

with ui.ImageContext(dw,dh) as ctx:
    with ui.ImageContext(iw,ih) as cropx:
        the_image.clip_to_mask(dx,dy,dw,dh)
        the_image.draw()
        le_crop = cropx.get_image()
    ui.concat_ctm(ui.Transform.translation(-dx, -dy))
    le_crop.draw()
    self.cropped_image = ctx.get_image()

Creating another view to show the outcome was easy, saving it to the Camera Roll even more thanks to the photos module, then I wrapped everything by creating a Label that would follow your selection with its real dimensions, so if you want to crop an image to turn it 640 pixels wide, you have a reference.

I promised more Editorial workflows and I believe this is a good start. You can download this workflow from the Editorial Workflow Directory. I hope you enjoy it.


  1. And I bet he is working on many others as you read this article, perhaps for an update of his book.