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.

Version 1.1

- Layout issues: if you change the orientation of your device, the crop area will adjust accordingly.
- Crop Adjustment: After selecting your crop area, now you can drag the corner handlers to adjust size or the crop area for position.
- Weird new bug: After you select your crop area, the Clear/Crop buttons look disabled, but they're enabled, I swear. If you crop and cancel, they'll work properly.

Shared by: Phillip Gruneich

Comments: Comment Feed (RSS)

There are no comments yet.

+ Add Comment

Workflow Preview
Run Python Script ?
Source Code
#coding: utf-8
import ui
import photos
		
#### PointView: the little handlers on the corners of the crop area

class PointView(ui.View):
	def __init__(self):
		self.bounds = (0,0,44,44)
		self.hidden = True
		self.touch_enabled = False
		self.flex = 'TBLR'

	def draw(self):
		contour = ui.Path.oval(14,14,16,16)
		ui.set_color('#FF7349')
		contour.fill()
		oval = ui.Path.oval(18,18,8,8)
		ui.set_color('#FF8B3F')
		oval.fill()

#### CropArea: the almighty area that defines where you'll crop the image
		
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)
		self.flex = 'WHTBLR'

##### DragView: the slutty view that lets you drag your finger all over it. CakeView?

class DragView (ui.View):
	def __init__(self):
		self.background_color = (1,1,1,0)
		self.content_mode = ui.CONTENT_SCALE_ASPECT_FIT
		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.flex = 'TBLR'
		self.crop_pixels.hidden = True
		self.crop_pixels.text_color = '#383838'
		self.crop_pixels.height = 20
		self.crop_pixels.alignment = ui.ALIGN_CENTER
		self.add_subview(self.crop_pixels)
		self.add_subview(self.crop_area)
		self.add_subview(self.indicator_start)

	def draw(self):
		self.crop_button = ui.ButtonItem(title='Crop', enabled=False, action=self.crop_action, tint_color=(1,0.54,0.24,1.0))
		self.superview.right_button_items = [self.crop_button]
		self.clear_button = ui.ButtonItem(title='Clear', enabled=False, action=self.clear_action, tint_color=(1,0.54,0.24,1.0))
		self.superview.left_button_items = [self.clear_button]
		self.spinner = ui.ActivityIndicator()
		self.spinner.style = ui.ACTIVITY_INDICATOR_STYLE_WHITE_LARGE
		self.spinner.center = self.center
		self.superview.add_subview(self.spinner)

	def touch_began(self, touch):
		self.crop_button.enabled = False
		self.clear_button.enabled = False
		if not self.indicator_end in self.subviews:
			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
		else:
			tx, ty = touch.location
			ax, ay, aw, ah = self.crop_area.frame
			axx, ayy = ax + aw, ay + ah
			sx, sy, sw, sh = self.indicator_start.frame
			sxx, syy = sx + sw, sy + sh
			ex, ey, ew, eh = self.indicator_end.frame
			exx, eyy = ex + ew, ey + eh
			if ax < tx < axx and ay < ty < ayy:
				cx, cy = self.crop_area.center
				self.drag_object = 0
				self.objx, self.objy = cx - tx, cy - ty
			elif sx < tx < sxx and sy < ty < syy:
				cx, cy = self.indicator_start.center
				self.drag_object = 1
				self.objx, self.objy = tx - cx, ty - cy
			elif ex < tx < exx and ey < ty < eyy:
				cx, cy = self.indicator_end.center
				self.drag_object = 2
				self.objx, self.objy = tx - cx, ty - cy
			else:
				self.drag_object = None
				self.remove_subview(self.indicator_end)
				self.touch_began(touch)

	def touch_moved(self, touch):
		tx,ty = touch.location
		if not self.indicator_end in self.subviews:
			if tx < self.indicator_start.center[0]:
				self.crop_area.x = tx
			if ty < self.indicator_start.center[1]:
				self.crop_area.y = ty
			self.crop_area.width = abs(tx - self.indicator_start.center[0])
			self.crop_area.height = abs(ty - self.indicator_start.center[1])
			self.crop_pixels.hidden = False
			self.crop_pixels.text = '%i x %i' % self.current_crop_size()
		else:
			if self.drag_object == 0:
				self.crop_area.center = self.objx + tx, self.objy + ty
				self.indicator_start.center = self.crop_area.x, self.crop_area.y
				self.indicator_end.center = self.crop_area.x + self.crop_area.width, self.crop_area.y + self.crop_area.height
			elif self.drag_object == 1:
				self.indicator_start.center = self.objx + tx, self.objy + ty
				self.crop_area.width = abs(self.indicator_end.center[0] - self.indicator_start.center[0])
				self.crop_area.height = abs(self.indicator_end.center[1] - self.indicator_start.center[1])
				self.crop_area.x, self.crop_area.y = self.indicator_start.center
				self.crop_pixels.text = '%i x %i' % self.current_crop_size()
			elif self.drag_object == 2:
				self.indicator_end.center = self.objx + tx, self.objy + ty
				self.crop_area.width = abs(self.indicator_end.center[0] - self.indicator_start.center[0])
				self.crop_area.height = abs(self.indicator_end.center[1] - self.indicator_start.center[1])
				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.crop_button.enabled = True
		self.clear_button.enabled = True
		if not self.indicator_end in self.subviews:
			self.add_subview(self.indicator_end)
			self.indicator_end.center = touch.location
			self.indicator_end.hidden = False
		
	def clear_action(self, sender):
		self.crop_button.enabled = False
		self.indicator_end.hidden = True
		self.remove_subview(self.indicator_end)
		self.crop_area.hidden = True
		self.indicator_start.hidden = True
		self.crop_pixels.hidden = True
		self.clear_button.enabled = False

	def crop_action(self, sender):
		self.spinner.start()
		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:
				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()
		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'
		self.spinner.stop()
		save_image = ui.ButtonItem(title='Save Image', action=self.save_to_camera_roll)
		self.show_crop.right_button_items = [save_image]
		self.show_crop.present('sheet')

	def save_to_camera_roll(self, sender): ### Saves the cropped image to the Camera Roll
		saved_photo = photos.save_image(self.cropped_image)
		if saved_photo:
			self.show_crop.close()

	def current_crop_size(self): ## Calculates the real crop size to fill the label.
		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

	def layout(self): ## Fixes the position for the label that displays the actual pixels on device turn.
		self.crop_pixels.x = self.crop_area.x
		self.crop_pixels.y = self.crop_area.y + self.crop_area.height
		
###### DemoView: the patriarch of all views.

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.add_subview(self.image_view)
		self.drag_view = DragView()
		self.add_subview(self.drag_view)

	def set_image(self, img):
		self.image_view.image = img
		self.layout()

	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()
	v.set_image(img)
	v.present()