Editorial Workflows

Crop Image

public workflow

Install Workflow...

This workflow contains at least one Python script. Only use it if you trust the person who shared this with you, and if you know exactly what it does.

I understand, install the workflow!

This is a workflow for Editorial, a Markdown and plain text editor for iOS. To download it, you need to view this page on a device that has the app installed.

Description: Custom UI to crop images and save the output to the Camera Roll.

Shared by: Phillip Gruneich

Comments: Comment Feed (RSS)

Storm Garelli — 08 Jun 2014
Thanks for this Phillip. I'm looking for a decent precision cropping tool and I'm hoping that this workflow can be the basis of one.

I'm not 100% sure that it's working correctly on my retina iPad Mini though. Am I right to assume that it's supposed to be possible to adjust the orange selection rectangle using its little circular handles? And also to move the rectangle by tapping and dragging within it? Neither of those things work for me as every tap starts the creation of a new rectangle (i.e. No adjusting or moving the existing rectangle). Maybe that wasn't even your intention, but maybe it's a bug. I thought you might like to know anyway.

Thanks again for producing this great workflow.



Phillip Gruneich — 08 Jun 2014
Hey, nops. I didn't add those features (at least not yet) since it has minimum features to make the workflow good enough. The pieces are there though, you just have to create more custom views to accomodate the additional touch gestures.

+ Add Comment

Workflow Preview
Run Python Script ?
Source Code
#coding: utf-8
import ui
import photos

class PointView(ui.View):
	def __init__(self):
		self.bounds = (0,0,16,16)
		self.hidden = True
		self.touch_enabled = False
	def draw(self):
		contour = ui.Path.oval(0,0,self.width, self.height)
		oval = ui.Path.oval(4,4,self.width - 8, self.height - 8)
class CropArea(ui.View):
	def __init__(self):
		self.hidden = True
		self.touch_enabled = False
		self.background_color = (1,0.54,0.24,0.3)
		self.bounds = (0,0,2,2)

class DragView (ui.View):
	def __init__(self):
		self.background_color = (1,1,1,0)
		self.crop_area = CropArea()
		self.indicator_start = PointView()
		self.indicator_end = PointView()
		self.crop_pixels = ui.Label(number_of_lines=1, background_color=(1,0.54,0.24,1.0))
		self.crop_pixels.hidden = True
		self.crop_pixels.text_color = '#383838'
		self.crop_pixels.height = 20
		self.crop_pixels.alignment = ui.ALIGN_CENTER
	def draw(self):
		self.crop_button = ui.ButtonItem(title='Crop', enabled=False, action=self.crop_action)
		self.superview.right_button_items = [self.crop_button]
		self.clear_button = ui.ButtonItem(title='Clear', enabled=False, action=self.clear_action)
		self.superview.left_button_items = [self.clear_button]
		self.spinner = ui.ActivityIndicator()
		self.spinner.center = self.center
	def touch_began(self, touch):
		self.crop_button.enabled = False
		self.clear_button.enabled = False
		self.crop_area.bounds = (0,0,2,2)
		self.indicator_start.center = touch.location
		self.indicator_start.hidden = False
		self.crop_area.x = touch.location[0]
		self.crop_area.y = touch.location[1]
		self.crop_area.hidden = False
		self.crop_pixels.hidden = True

	def touch_moved(self, touch):
		loc = touch.location
		if loc[0] < self.indicator_start.center[0]:
			self.crop_area.x = loc[0]
		if loc[1] < self.indicator_start.center[1]:
			self.crop_area.y = loc[1]
		self.crop_area.width = abs(loc[0] - self.indicator_start.center[0])
		self.crop_area.height = abs(loc[1] - self.indicator_start.center[1])
		self.crop_pixels.hidden = False
		self.crop_pixels.text = '%i x %i' % self.current_crop_size()
		self.crop_pixels.x = self.crop_area.x
		self.crop_pixels.y = self.crop_area.y + self.crop_area.height
	def touch_ended(self, touch):
		self.indicator_end.center = touch.location
		self.indicator_end.hidden = False
		self.crop_button.enabled = True
		self.clear_button.enabled = True
	def clear_action(self, sender):
		self.crop_button.enabled = False
		self.indicator_end.hidden = True
		self.crop_area.hidden = True
		self.indicator_start.hidden = True
	def crop_action(self, sender):
		the_image = self.superview.image_view.image
		iw, ih = the_image.size
		fx, fy, fw, fh = self.crop_area.frame
		scale = min(iw/self.width, ih/self.height)
		dw, dh, dx, dy = fw * scale, fh * scale, fx * scale, fy * scale
		with ui.ImageContext(dw,dh) as ctx:
			with ui.ImageContext(iw,ih) as cropx:
				le_crop = cropx.get_image()
			ui.concat_ctm(ui.Transform.translation(-dx, -dy))
			self.cropped_image = ctx.get_image()
		self.show_crop = ui.ImageView()
		self.show_crop.image = self.cropped_image
		self.show_crop.content_mode = ui.CONTENT_SCALE_ASPECT_FIT
		self.show_crop.flex = 'TBLR'
		save_image = ui.ButtonItem(title='Save Image', action=self.save_to_camera_roll)
		self.show_crop.right_button_items = [save_image]
	def save_to_camera_roll(self, sender):
		saved_photo = photos.save_image(self.cropped_image)
		if saved_photo:
	def current_crop_size(self):
		iw, ih = self.superview.image_view.image.size
		fx, fy, fw, fh = self.crop_area.frame
		scale = min(iw/self.width, ih/self.height)
		dw, dh = fw * scale, fh * scale
		return dw,dh

class DemoView (ui.View):
	def __init__(self):
		self.image_view = ui.ImageView(frame=self.bounds)
		self.image_view.content_mode = ui.CONTENT_SCALE_ASPECT_FIT
		self.image_view.flex = 'WH'
		self.image_view.background_color = '#383838'
		self.drag_view = DragView()
	def set_image(self, img):
		self.image_view.image = img
	def layout(self):
		if self.image_view.image:
			w, h = self.width, self.height
			iw, ih = self.image_view.image.size
			scale = min(w/iw, h/ih)
			dw, dh = iw * scale, ih * scale
			x, y = (w - dw) * 0.5, (h - dh) * 0.5
			self.drag_view.frame = (x, y, dw, dh)
img = ui.Image.from_data(photos.pick_image(raw_data=True))
if img:
	v = DemoView()