omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    Which version of Pythonista is this code?

    Pythonista
    3
    4
    2473
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • superrune
      superrune last edited by

      Hi!

      I'm learning Python, and just started looking at Pythonista. I want to modify some existing code to fit my project, specifically this pixel editor I found on GitHub. The code doesn't run, I assume it was written for an earlier version of Pythonista. Is there a way to see which version the code was written for?

      https://github.com/SebastianJarsve/Pythonista-Pixel-Editor

      1 Reply Last reply Reply Quote 0
      • JonB
        JonB last edited by

        Your best bet is to post the exceptions... since that code was posted 3 years agos, it was pribably from 1.5 or 1.6, the main difference was that was pythin 2.7 -- you might first try adding a #!python2 shebang at the top of the script and see if that helps, if so you can either run the 2-3 converter, or manually change whatever the problems are. (the BytesIO/StringIO is a good bet)

        1 Reply Last reply Reply Quote 0
        • omz
          omz last edited by

          The main problem seems to be that scene.Rect isn’t subclassable anymore, and that ui.View.did_load has a slightly different behavior. Below is a patched pixel_editor.py that should work in Pythonista 3:

          #!python2
          
          import console, scene, photos, clipboard, ui, io, os.path, Image, numpy
          
          def pil_to_ui(img):
          	with io.BytesIO() as bIO:
          		img.save(bIO, 'png')
          		return ui.Image.from_data(bIO.getvalue())
          	
          def ui_to_pil(img):
          	return Image.open(io.BytesIO(img.to_png()))
          	
          def crop_image(image):
          	image = ui_to_pil(image)
          	image_data = numpy.asarray(image)
          	image_data_bw = image_data.max(axis=2)
          	non_empty_columns = numpy.where(image_data_bw.max(axis=0)>0)[0]
          	non_empty_rows = numpy.where(image_data_bw.max(axis=1)>0)[0]
          	cropBox = (min(non_empty_rows), max(non_empty_rows), min(non_empty_columns), max(non_empty_columns))
          	image_data_new = image_data[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
          	new_image = pil_to_ui(Image.fromarray(image_data_new))
          	return new_image
          
          class Pixel (object):
          	def __init__(self, x, y, w, h):
          		self.rect = scene.Rect(x, y, w, h)
          		self.colors = [(0, 0, 0, 0)]
          		
          	def used(self):
          		return len(self.colors) > 1 and self.colors[-1] != (0, 0, 0, 0)
          		
          	def undo(self):
          		if len(self.colors) > 1:
          			self.colors.pop()
          
          class PixelEditor(ui.View):
          	def did_load(self):
          		self.row = self.column = 16
          		self.pixels = []
          		self.pixel_path = []
          		self.image_view = self.create_image_view()
          		self.grid_layout = self.create_grid_layout()
          		self.current_color = (0, 0, 0, 1)
          		self.mode = 'pencil'
          		self.auto_crop_image = False 
          		
          	def has_image(self):
          		if self.pixel_path:
          			if [p for p in self.pixel_path if p.used()]:
          				return True 
          		return False 
          
          	def set_image(self, image=None):
          		image = image or self.create_new_image()
          		self.image_view.image = self.superview['preview'].image = image
          		
          	def get_image(self):
          		image = self.image_view.image
          		if self.auto_crop_image:
          			return crop_image(image)
          		return image
          		
          	def add_history(self, pixel):
          		self.pixel_path.append(pixel)
          
          	def create_grid_image(self):
          		s = self.width/self.row if self.row > self.column else self.height/self.column
          		path = ui.Path.rect(0, 0, *self.frame[2:])
          		with ui.ImageContext(*self.frame[2:]) as ctx:
          			ui.set_color((0, 0, 0, 0))
          			path.fill()
          			path.line_width = 2
          			for y in xrange(self.column):
          				for x in xrange(self.row):
          					pixel = Pixel(x*s, y*s, s, s)
          					path.append_path(ui.Path.rect(*pixel.rect))
          					self.pixels.append(pixel)
          			ui.set_color('gray')
          			path.stroke()
          			return ctx.get_image()
          
          	def create_grid_layout(self):
          		image_view = ui.ImageView(frame=self.bounds)
          		image_view.image = self.create_grid_image()
          		self.add_subview(image_view)
          		return image_view
          
          	def create_image_view(self):
          		image_view = ui.ImageView(frame=self.bounds)
          		image_view.image = self.create_new_image()
          		self.add_subview(image_view)
          		return image_view
          
          	def create_new_image(self):
          		path = ui.Path.rect(*self.frame)
          		with ui.ImageContext(self.width, self.width) as ctx:
          			ui.set_color((0, 0, 0, 0))
          			path.fill()
          			return ctx.get_image()
          
          	def create_image_from_history(self):
          		path = ui.Path.rect(*self.frame)
          		with ui.ImageContext(self.width, self.height) as ctx:
          			for pixel in self.pixel_path:
          				if not pixel.used():
          					continue 
          				ui.set_color(pixel.colors[-1])
          				pixel_path = ui.Path.rect(*pixel.rect)
          				pixel_path.line_width = 0.5
          				pixel_path.fill()
          				pixel_path.stroke()
          			img = ctx.get_image()
          			return img
          			
          	def reset(self, row=None, column=None):
          		self.row = row or self.row
          		self.column = column or self.column
          		self.pixels = []
          		self.pixel_path = []
          		self.grid_layout.image = self.create_grid_image()
          		self.set_image()
          
          	def undo(self):
          		if self.pixel_path:
          			pixel = self.pixel_path.pop()
          			pixel.undo()
          			self.set_image(self.create_image_from_history())
          
          	def pencil(self, pixel):
          		if pixel.colors[-1] != self.current_color:
          			if self.current_color != (0, 0, 0, 0):
          				pixel.colors.append(self.current_color)
          				self.pixel_path.append(pixel)
          				old_img = self.image_view.image
          				path = ui.Path.rect(*pixel.rect)
          				with ui.ImageContext(self.width, self.height) as ctx:
          					if old_img:
          						old_img.draw()
          					ui.set_color(self.current_color)
          					pixel_path = ui.Path.rect(*pixel.rect)
          					pixel_path.line_width = 0.5
          					pixel_path.fill()
          					pixel_path.stroke()
          					self.set_image(ctx.get_image())
          
          	def eraser(self, pixel):
          		if pixel.used():
          			pixel.colors.append((0, 0, 0, 0))
          			self.pixel_path.append(pixel)
          			img = self.create_image_from_history()
          			self.set_image(self.create_image_from_history())
          
          	def color_picker(self, pixel):
          		self.current_color = pixel.colors[-1]
          		self.superview['colors'].set_color(pixel.colors[-1])
          
          	def action(self, touch):
          		p = scene.Point(*touch.location)
          		for pixel in self.pixels:
          			if p in pixel.rect:
          				eval('self.{}(pixel)'.format(self.mode))
          
          	def touch_began(self, touch):
          		self.action(touch)
          
          	def touch_moved(self, touch):
          		self.action(touch)
          
          class ColorView (ui.View):
          	def did_load(self):
          		self.color = {'r':0, 'g':0, 'b':0, 'a':1}
          		for subview in self.subviews:
          			self.init_action(subview)
          
          	def init_action(self, subview):
          		if hasattr(subview, 'action'):
          			subview.action = self.choose_color if subview.name != 'clear' else self.clear_user_palette
          		if hasattr(subview, 'subviews'):
          			for sv in subview.subviews:
          				self.init_action(sv)
          
          	def get_color(self):
          		return tuple(self.color[i] for i in 'rgba')
          
          	def set_color(self, color=None):
          		color = color or self.get_color()
          		for i, v in enumerate('rgba'):
          			self[v].value = color[i]
          			self.color[v] = color[i]
          		rgb_to_hex = tuple(int(i*255) for i in color[:3])
          		self['color_input'].text = ''.join('#{:02X}{:02X}{:02X}'.format(*rgb_to_hex))
          		self['current_color'].background_color = color
          		self.superview['editor'].current_color = color
          
          	@ui.in_background
          	def choose_color(self, sender):
          		if sender.name in self.color:
          			self.color[sender.name] = sender.value
          			self.set_color()
          		elif sender in self['palette'].subviews:
          			self.set_color(sender.background_color)
          		elif sender.name == 'color_input':
          			try: 
          				c = sender.text if sender.text.startswith('#') else eval(sender.text)
          				v = ui.View(background_color=c)
          				self['color_input'].text = str(v.background_color)
          				self.set_color(v.background_color)
          			except Exception as e:
          				console.hud_alert('Invalid Color', 'error')
          
          class ToolbarView (ui.View):
          	def did_load(self):
          		pass
          		#self.pixel_editor = self.superview['editor']
          		#for subview in self.subviews:
          		#	self.init_actions(subview)
          
          	def init_actions(self, subview):
          		if hasattr(subview, 'action'):
          			if hasattr(self, subview.name):
          				subview.action = eval('self.{}'.format(subview.name))
          			else: 
          				subview.action = self.set_mode
          		if hasattr(subview, 'subviews'):
          			for sv in subview.subviews:
          				self.init_actions(sv)
          				
          	def show_error(self):
          		console.hud_alert('Editor has no image', 'error', 0.8)
          		
          	@ui.in_background		
          	def trash(self, sender):
          		if self.pixel_editor.has_image():
          			msg = 'Are you sure you want to clear the pixel editor? Image will not be saved.'
          			if console.alert('Trash', msg, 'Yes'):
          				self.pixel_editor.reset()
          		else: 
          			self.show_error()
          			
          	@ui.in_background
          	def save(self, sender):
          		if self.pixel_editor.has_image():
          			image = self.pixel_editor.get_image()
          			option = console.alert('Save Image', '', 'Camera Roll', 'New File', 'Copy image')
          			if option == 1:
          				photos.save_image(image)
          				console.hud_alert('Saved to cameraroll')
          			elif option == 2:
          				name = 'image_{}.png'
          				get_num = lambda x=1: get_num(x+1) if os.path.isfile(name.format(x)) else x
          				file_name = name.format(get_num())
          				with open(file_name, 'w') as f:
          					ui_to_pil(image).save(f, 'png')
          				console.hud_alert('Image saved as "{}"'.format(file_name))
          			elif option == 3:
          				clipboard.set_image(image, format='png')
          				console.hud_alert('Copied')
          		else: 
          			self.show_error()
          			
          	def undo(self, sender):
          		self.pixel_editor.undo()
          				
          	@ui.in_background
          	def preview(self, sender):
          		if self.pixel_editor.has_image():
          			v = ui.ImageView(frame=(100,400,512,512))
          			v.image = self.pixel_editor.get_image()
          			v.width, v.height = v.image.size
          			v.present('popover', popover_location=(200, 275), hide_title_bar=True)
          		else: 
          			self.show_error()
          			
          	def crop(self, sender):
          		if not self.pixel_editor.auto_crop_image:
          			sender.background_color = '#4C4C4C'
          			sender.tint_color = 'white'
          			self.pixel_editor.auto_crop_image = True
          		else: 
          			sender.background_color = (0, 0, 0, 0)
          			sender.tint_color = 'black'
          			self.pixel_editor.auto_crop_image = False 
          
          	@ui.in_background
          	def pixels(self, sender):
          		if self.pixel_editor.has_image():
          			console.hud_alert("Can't chage size while editing.", "error")
          			return 
          		try: 
          			size = eval(sender.text)
          			row, column = (size if isinstance(size, tuple) else (size, size))
          			self.pixel_editor.reset(row, column)
          			self['pixels'].text = '{},{}'.format(row, column)
          		except Exception as e:
          			console.hud_alert('Invalid size', 'error', 0.8)
          		
          	def set_mode(self, sender):
          		self.pixel_editor.mode = sender.name
          		for b in self['tools'].subviews:
          			b.background_color = tuple((0, 0, 0, 0))
          		sender.background_color = '#4C4C4C'
          
          v = ui.load_view('pixel_editor')
          toolbar = v['toolbar']
          toolbar.pixel_editor = v['editor']
          for subview in toolbar.subviews:
          	toolbar.init_actions(subview)
          v.present(orientations=['portrait'])
          
          
          1 Reply Last reply Reply Quote 0
          • superrune
            superrune last edited by

            Fantastic! Thanks for going through the code, this is super-helpful. Thanks again!

            1 Reply Last reply Reply Quote 0
            • First post
              Last post
            Powered by NodeBB Forums | Contributors