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 an
ImageContext, 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.
And I bet he is working on many others as you read this article, perhaps for an update of his book. ↩