Forum Archive

Containers for photos, with scroll, drag&drop between them

jmv38

Hello folks,

I am not happy with the various solutions to manage my photos on the ipad. I would like to have a panel with
- a container with a thumbnail of my photos, in 1 d, scollable
- several containers in which i can d&d the photos I want to assemble
- for each of those a page in wich i can arrange the selected photos in the layout i want
- save the layout in camera roll when i am happy with it
- save all the containers data so i can rework my choices.

ideally all these is real time. This is to prepare my printed photoalbums before ordering them (the online tools are way too slow and cumbersome and unaccurate to do the preparation work on them - I usually use ‘Print To Size’ app for that).
I think pythonista is able to do that. But i am not (yet).

Could you guys point out for me some existing code bricks you know about that I could re-use and assemble for this project?
I already heave found some excellent gesture repos, but not the scrollable photo container. I could do it myself but considering my poor python skills it is going to take me 2 or 3 weeks, so if i can use already available code, that’s good to spare the effort for the overall app. And i have noticed it is much easier for me to start from someone else good code and bend it to my will, rather than starting from scratch myself.

Please help me!
Thanks

cvp

@jmv38 Have à look to this, far from perfect, little script

# todo
# - ImageView zoom and scroll
#   - limit au min
from  Gestures  import Gestures
from  objc_util import *
import os
import photos
import ui

class PhotosPickerTableView(ui.View):

    def __init__(self,w,h,d):
        self.width = w
        self.height = h
        self.selected = None

        ok = ui.ButtonItem()
        ok.image = ui.Image.named('iob:ios7_checkmark_outline_32')
        ok.action = self.ok_action
        self.right_button_items = (ok,)

        tbl = ui.TableView()    
        tbl.frame = (0,0,d,h)
        tbl.row_height = d
        self.assets = photos.get_assets()
        tbl.data_source = ui.ListDataSource(items=range(len(self.assets)))
        tbl.separator_color=(1,0,0,0)
        tbl.delegate = self
        tbl.data_source.tableview_cell_for_row = self.tableview_cell_for_row
        tbl.background_color = (0,0,0,0)
        self.add_subview(tbl)

        pha =ui.View(name='photo_area')     
        pha.frame = (d,0,w-d,h)
        self.add_subview(pha)
        img = ui.ImageView(name='selected_photo')
        img.frame = (0,0,w-d,h)
        img.content_mode=ui.CONTENT_SCALE_ASPECT_FIT
        img.background_color = (0.5,0.5,0.5,1)
        pha.add_subview(img)

        self.tableview_did_select(tbl,0,0)

        self.photo_scale = 1        
        self.g = Gestures()
        self.pinch = self.g.add_pinch(img,self.did_pinch)       
        self.pan   = self.g.add_pan(img,self.did_pan,1,1)     

    def did_pinch(self, data):
        if data.state == 1:
            self.scale_at_begin = self.photo_scale
        s = data.scale
        s_new = self.photo_scale * s
        #print('did_pinch: data.scale=',s,'scale:',self.photo_scale,'-->',s_new,'data.state=',data.state)
        if s_new < 1 and self.scale_at_begin >= 1:
            # minimum size 1
            s_new = 1
            s = 1 / self.photo_scale

        #if s_new > 20:
        #   return
        self.photo_scale = s_new
        #print('scale',s_new)
        self['photo_area']['selected_photo'].transform = self['photo_area']['selected_photo'].transform.concat(self['photo_area']['selected_photo'].transform.scale(s,s))
        self.pinch.scale = 1.0

    def did_pan(self, data):
        #print('before',self['photo_area']['selected_photo'].frame)
        o = data.translation
        o = o * self.photo_scale    # to accelerate move if zoom
        #print('translation',o)
        if ((self['photo_area']['selected_photo'].frame[0]+o[0]) > 0) or ((self['photo_area']['selected_photo'].frame[1]+o[1]) > 0):
            return
        self['photo_area']['selected_photo'].transform = self['photo_area']['selected_photo'].transform.concat(self['photo_area']['selected_photo'].transform.translation(*o))
        self.p = CGPoint(0,0)
        self.pan.setTranslation_inView_(self.p,self.pan.view())
        #print('after',self['photo_area']['selected_photo'].frame)


    def ok_action(self,sender):
        self.close()

    def tableview_did_select(self, tableview, section, row):
        ui_image = self.assets[row].get_ui_image()
        self['photo_area']['selected_photo'].image = ui_image
        self.selected = row

    def tableview_cell_for_row(self,tableview, section, row):
        cell = ui.TableViewCell()
        v = 0.4 + (row % 2)*0.4
        cell.bg_color = (v,v,v,v)
        selected_cell = ui.View()
        #selected_cell.bg_color = 'blue'
        selected_cell.border_width = 2
        selected_cell.border_color = 'blue'
        cell.selected_background_view = selected_cell
        photo = ui.ImageView()
        photo.frame = (0,0,tableview.row_height,tableview.row_height)
        photo.image = self.assets[row].get_ui_image()
        photo.content_mode = ui.CONTENT_SCALE_ASPECT_FIT
        cell.content_view.add_subview(photo)
        return cell

def main():

    # Hide script
    w,h = ui.get_screen_size()
    mi = min(w,h)*0.9
    my_back = PhotosPickerTableView(mi,mi,100)
    my_back.background_color='white'
    my_back.name = 'Photos Picker via TableView'    

    my_back.present('sheet',hide_title_bar=False)   
    my_back.wait_modal()

# Protect against import    
if __name__ == '__main__':
    main()  
jmv38

@cvp wow! that was fast!!!
thanks a lot. This is pretty much exactly the first container i needed.
Now i should add other empty containers (perpendicular) and the drag and drop mechanism.
This really a good kickstart, thanks!

cvp

@jmv38 That has been fast because I already had written it 😂

cvp

@jmv38 Little code, extracted from a big program, to show how to select a grid type.
Some code may seem strange because used by removed parts.
My big program, not yet ended, is used by my wife to join pictures in several different grids with moving lines. Program not completely operational and with' French messages.

import ImageFont
import ui

def my_list_popover(elements,val=None,x=None,y=None,color='white',multiple=False,title=None,selectable=True):
    global grids
    class my_list(ui.View):
        def __init__(self,selected):

            # In multiple selection mode, an "ok" button is needed
            if multiple:
                ok_button = ui.ButtonItem()
                ok_button.title = 'ok'
                ok_button.action = self.ok_button_action
                self.right_button_items = [ok_button]

            # ListDataSource for clients TableView
            elements_lst = ui.ListDataSource(items=lst)
            # ListDataSource has no attribute "name"
            elements_lst.delete_enabled = False
            elements_lst.move_enabled = False
            elements_txt = ui.TableView(name='elements_txt')
            elements_txt.allows_multiple_selection = multiple
            elements_txt.allows_selection = selectable
            elements_txt.text_color = 'black'
            elements_txt.font= ('Courier',12)
            if elements == grids:
                elements_txt.row_height = 128
            else:
                elements_txt.row_height = elements_txt.font[1] * 2
            h = len(lst) *  elements_txt.row_height
            font = ImageFont.truetype('Courier',16)
            w = 0

            if elements == grids:
                ws, hs = ui.get_screen_size()
                w = int(elements_txt.row_height*ws/hs)
                h_ele = elements_txt.row_height
                h_max = h_screen*0.9
            else:
                for element in lst:
                    w_ele,h_ele = font.getsize(element)
                    if w_ele > w:
                        w = w_ele
                w = w + 32
                h_max = h_screen*0.8
            elements_txt.content_size = (w,h)
            if h > h_max:
                h = h_max
            w_max = w_screen - 20
            if w > w_max:
                w = w_max
            elements_txt.frame = (4,4,w,h)  
            #elements_txt.border_color = 'blue'
            #elements_txt.border_width = 3
            elements_txt.data_source = elements_lst
            elements_txt.data_source.tableview_cell_for_row = self.tableview_cell_for_row
            if not multiple:
                if selected >= 0:
                    elements_txt.selected_row = (0,selected)
            else:
                if selected_lst != []:
                    elements_txt.selected_rows = selected_lst

            elements_txt.delegate  = self
            self.add_subview(elements_txt)  
            self.width = elements_txt.width   + 8
            self.height = elements_txt.height + 8
            self.corner_radius = 20
            self.border_color = 'blue'
            self.border_width = 2
            self.background_color = color
            if title and multiple:
                # title bar displayed in multiple
                self.name = title
            self.pending = []

        def ok_button_action(self,sender):
            lst.append(str(multiples)) # '['xxxx','yyyy']'
            v.close()

        def tableview_did_deselect(self, tableview, section, row):
            # Called when a row was deselected (multiple only)
            if multiple:
                multiples.remove(lst[row])
                #print(multiples)

        def tableview_did_select(self, tableview, section, row):
            # Called when a row was selected
            if multiple:
                multiples.append(lst[row])              # ['xxxx','yyyy']
                #print(multiples)
            else:
                lst.append(lst[row])
                v.close()

        def tableview_accessory_button_tapped(self, tableview, section, row):
            # Called when the accessory (ex: info) tapped
            if multiple:
                multiples.append('tapped'+lst[row])
                lst.append(str(multiples))      # '['xxxx','yyyy','tapped.....']'
            else:
                lst.append('tapped'+lst[row]) # 'tapped...'
            v.close()

        def tableview_cell_for_row(self, tableview, section, row):
            cell = ui.TableViewCell()
            selected_cell = ui.View()
            selected_cell.border_color = 'black'
            selected_cell.border_width = 2
            selected_cell.corner_radius = 10
            data = tableview.data_source.items[row]
            if elements == grids:
                selected_cell.bg_color = 'lightgray'
            else:
                selected_cell.bg_color = 'cornflowerblue'
            cell.selected_background_view = selected_cell

            cell.text_label.text = data
            cell.text_label.alignment = ui.ALIGN_LEFT
            ch1 = cell.text_label.text[0]                       # 1st char of text
            if ch1 == '🈸':
                g = ui.Button()
                g.background_color = 'white'
                g.image = ui.Image.named('iob:grid_32')
                g.width  = 28
                g.height = 28
                g.x = 12
                g.y = 0
                cell.content_view.add_subview(g)    
            cell.text_label.text_color = 'black'
            cell.bg_color = color

            if elements == grids:
                cell.text_label.text = ''
                h = tableview.row_height
                w = tableview.width
                rows = data.split('/')
                ny = len(rows)
                x0 = 2
                y0 = 5
                dy = int((h-2*y0)/ny)   
                y = y0
                bs = {}
                for iy in range(0,ny):              # loop on rows
                    x = x0
                    nx = len(rows[iy])                  # should be equal on each row
                    dx = int((w-2*x0)/nx)   
                    for ix in range(0,nx):
                        c = rows[iy][ix]                    # cell n°
                        if c not in bs:
                            b = ui.Button()
                            b.enabled = False
                            if ix < (nx-1):
                                wx = dx
                            else:
                                wx = (w-2*x0) - x # force all last right same
                            b.border_width = 1
                            b.border_color = 'blue'
                            b.title = c
                            b.tint_color ='blue'
                            b.frame = (x,y,wx,dy)
                        else:
                            # split cell on several rows or columns: dx or dy will change
                            b = bs[c]
                            if b.y == y:
                                b.width = (x + wx - 1) - b.x + 1 # same row
                            else:
                                b.height = (y + dy - 1) - b.y + 1 # same col
                        bs[c] = b                           
                        k = 'Photo ' + c
                        cell.content_view.add_subview(b)
                        x = x + dx - 1
                    y = y + dy - 1
                del bs
            return cell     

    lst = []
    i = 0
    selected = -1
    if type(elements) is dict:
        elements_list = sorted(elements.keys())
    elif type(elements) is list:
        elements_list = elements
    if multiple:
        # val = ['xxx','yyy']
        if val:  # not None
            val_lst = ast.literal_eval(val) # convert str -> dict
        else:
            val_lst = []
        selected_lst = []
        multiples = []
    for element in elements_list:
        lst.append(element)
        if not multiple:
            if element == val:
                selected = i
        else:
            if element in val_lst:
                selected_lst.append((0,i))
                multiples.append(element)
        i = i + 1
    w_screen,h_screen = ui.get_screen_size()
    if not x:
        x = int(w_screen/2)
    if not y:
        y = int(h_screen/2)
    v = my_list(selected)
    # lst already copied into elements_lst, thus we can change lst
    # to contain iniital value in case of tap outside
    if selected == -1:
        lst.append('')
    else:
        lst.append(lst[selected])
    if not multiple:
        v.present('popover',popover_location=(x,y),hide_title_bar=True)
    else:
        v.present('popover',popover_location=(x,y),hide_title_bar=False)
    v.wait_modal()
    return lst[len(lst)-1]  # return last

def main():
    global grids
    w,h = ui.get_screen_size()
    mi = min(w,h)*0.9
    mv = ui.View()
    mv.frame = (0,0,mi,mi)
    mv.name = 'Example of grids button'
    mv.background_color = 'white'
    grid = ui.ButtonItem()
    grid.image = ui.Image.named('iob:grid_32')
    grids = ['1', '12', '1/2','123', '11/23', '12/33',  '1/2/3', '12/32', '12/13', '1234', '111/234', '12/34', '123/444', '11/22/34', '12/33/44', '11/23/44', '1/2/3/4', '12/13/14', '12/32/42', '11/23/43', '123/143', '123/425', '1234/5678/9abc/defg', '12345/67890/abcde/fghij/klmno'] #, '5', '1/4', '4/1', '2/3', f'3/2', '1/1/3', '1/3/1', '2/1/2', '2/2/1', '1/2/2', '3/1/1','1/1/1/2', '1/1/2/1', '1/2/1/1', '2/1/1/1', '1/1/1/1/1']       
    def grid_action(sender):
        x = mi - 50
        y = 50
        g = my_list_popover(grids,-1,x=x,y=y,color='white')
    grid.action = grid_action
    mv.right_button_items = (grid,) 
    mv.present('sheet')

# Protect against import    
if __name__ == '__main__':
    main()
cvp

I could agree to put in GitHub my program but I don't want to provide any support because it often crashes with big or multiple photos 😢 and I don't have enough free time to debug it.

cvp

See also PhotosPickerView.py

jmv38

@cvp thanks for the 2nd example, it is inspiring.

Actually i dont use grids i prefer to place and zoom and cut each photo manually.

If you put your whole program in github i certainly wont demand support!

I think I’ll post my work step by step in gists and this forum as I progress. Not sure how fast I’ll go though...

Btw, I am french too (Grenoble) so no pb with the langage. I’ll continue anyway to write in english by respect for people in this forum.

Thanks.

jmv38

@cvp from the doc, it seems the tableView is vertical only. Is there a way to have is horizontal too? Initially i wanted my camera roll container to be vertical, but the photo group containers to be horizontal (and themselves inside a scrollable vertical container too).
So i should pbly use a scrollview instead for my base photo container?

jmv38

@cvp mmm PhotosPickerView.py seems a bit over my head, with all this objC functions...

cvp

@jmv38 said:

f you put your whole program in github i certainly wont demand support!

here

cvp

@jmv38 said:

seems a bit over my head, with all this objC functions...

It is not so complex as it seems...

jmv38

@cvp thanks for juxtapositionDePhotos.py
all the bricks i need are there
=> this is really going to help me.
thanks.

cvp

@jmv38 quick and dirty horizontal scroll view

import photos
import ui

def main():
    assets = photos.get_assets()
    #assets = photos.pick_asset(assets=assets,multi=True)
    d = 100
    wsv = min(400,d * len(assets))
    sv = ui.ScrollView()
    sv.frame = (0,0,wsv,d)
    x = 0
    for i in range(0,len(assets)):
        b = ui.Button(name=str(i))
        #b = ui.ImageView(name=str(i))
        b.border_color = 'blue'
        b.border_width = 1
        ui_image = assets[i].get_ui_image().with_rendering_mode(ui.RENDERING_MODE_ORIGINAL)
        w,h = ui_image.size
        w = d * w/h
        b.frame = (x,0,w,d)
        b.image = ui_image
        def b_action(sender):
            sender.superview.name = 'tapped=' + sender.name
        b.action = b_action
        sv.add_subview(b)
        x = x + w
    sv.content_size = (x,d)
    sv.present('sheet')

if __name__ == '__main__':
    main()

jmv38

@cvp excellent! thanks.
It is nice to have the function in a really small piece of code.
I had to limit the number of photos because i have 1600 and that crashes my app. I’ll manage that.

Another thing: for the interractions between panels i learned (from coding in Codea) that it is much easier to wire things together via a register/trigger message system. Do you have something available?

cvp

@jmv38 said:

it is much easier to wire things together via a register/trigger message system. Do you have something available?

No, never done like that. I always use normal way of ui. Good luck

cvp

@jmv38 said:

limit the number of photos because i have 1600

Perhaps, use collections or get_moments

jmv38

@cvp here an example of a very simple event class i found on google and modified for my needs. I also added an example of usage.

class EventHook(object):

    def __init__(self):
        self.__handlers = []

    def __iadd__(self, handler):
        self.__handlers.append(handler)
        return self

    def __isub__(self, handler):
        self.__handlers.remove(handler)
        return self

    def __call__(self, *args, **keywargs):
        for handler in self.__handlers:
            handler(*args, **keywargs)

    def clearObjectHandlers(self, inObject):
        for theHandler in self.__handlers:
            if theHandler.im_self == inObject:
                self -= theHandler

if __name__ == '__main__':
  class Watcher():
      def __init__(self):
          self.incoming = EventHook()
          self.leaving = EventHook()

  class Greeter():
    def __init__(self,me):
      self.me = me
    def sayHello(self, name):
      print('{}: hello Mister {}'.format(self.me,name))
    def sayGoodbye(self, name):
      print('{}: goobye Mister {}'.format(self.me,name))

  class Maid():
    def __init__(self,me):
      self.me = me
    def sayHello(self, name):
      print('{}: hello Mister {}'.format(self.me,name))
    def sayGoodbye(self, name):
      if name != 'DSK':
        print('{}: goobye Mister {}'.format(self.me,name))
      else:
        print('{}: You f... s.. o. a b...!'.format(self.me))

  from console import clear
  clear()
  Isee = Watcher()
  deskMan = Greeter('desk man Georges')
  maid = Maid('maid Sandra')

  # make the connections:
  Isee.incoming += lambda name: print('\nM. {} is comming...'.format(name))
  Isee.incoming += deskMan.sayHello
  Isee.incoming += maid.sayHello

  Isee.leaving += lambda name: print('\nM. {} is leaving...'.format(name))
  Isee.leaving += maid.sayGoodbye
  Isee.leaving += deskMan.sayGoodbye

  # remove listener from the event

  # fire event
  Isee.incoming('Clinton')
  Isee.leaving('Clinton')


cvp

@jmv38 Too complex for me 😀

jmv38

@cvp i can understand what you feel: it is exactly what i thought the first time saw this! It took me several months to start to understand it, but once i did, bam!, it opened a huge box of possibilties because it made a complex project so much simpler. But I wont try to convert you, you’ll see for yourself as i use it in my project if you think it is usefull...
Thanks.

jmv38

@cvp i am in the final polishing phase of my project.
I have pb with button icons: they render in color when i want a gray rendering (the one shown in the editor).
my code:

img = ui.Image('iow:refresh_32').with_rendering_mode(ui.RENDERING_MODE_TEMPLATE)
    clear_button.image = img

any suggestion?
thanks.

cvp

@jmv38 did you try ui.RENDERING_MODE_ORIGINAL

cvp

@jmv38 this is ok, I think

img = ui.Image('iow:refresh_32')#.with_rendering_mode(ui.RENDERING_MODE_ORIGINAL)
b.image = img
b.tint_color = 'gray'
jmv38

@cvp thanks.
but it still doesnt work: now the button shows in gray, not white, and is not visible on my gray background.
I wonder how to get it simply as it shows in the editor?

jmv38

@cvp you know what? it works with ORIGINAL
looks like omz inverted TEMPLATE and ORIGINAL values
thanks.

cvp

@jmv38 said:

but it still doesnt work: now the button shows in gray, not white, and is not visible on my gray background.
I wonder how to get it simply as it shows in the editor?

You asked gray and I didn't know you had a gray background

jmv38

@cvp i have finished my first version of the program
here https://gist.github.com/1b67ba85ca7bd7b23c7058216895372c
this will:
- let you choose an album xxxx
- load the 200 first pictures
- present them in a photopicker
- from which you can build photo album pages (collage)
- it creates 2 new albums:
- xxxx_pages : the album pages
- xxxx_pages_and_photos: as it says. useful to upload the album in photoweb printing service for instance
- your own photos and albums are not modified

to see how to use it tap ‘?’ button: it opens a youtube video that shows it in action.

It is designed to work in lanscape mode
let me know how it works for you.

jmv38

@cvp hello again!

I am trying to make a high definition image with draw_snapshot()
I works fine until the context width is 4000, but i get a black image when the context width is 5000 or more. I need 9000....
Any suggestion?
Thanks.

cvp

@jmv38 Sorry, I forgot to tell you, 20 days ago, I had tried your Appli.py it I don't have any album in my photos, thus I tap the X in the first screen, and your program crashes in line
inputAlbum = allAlbums[inputTitle] 😢

cvp

@jmv38 this works for me

import ui
iv = ui.ImageView()
iv.image = ui.Image.named('P1020096.JPG') # 3888 x 2592 pixels
wi,hi = iv.image.size
iv.frame = (0,0,wi,hi)
print(wi,hi)                                                            # 3888 2592
w = 2*wi
h = 2*hi
print(w,h)                                                              # 7776 5184
with ui.ImageContext(w,h) as ctx:
    iv.draw_snapshot()
    ui_image = ctx.get_image()
    with open('t1.jpg', 'wb') as f:
        f.write(ui_image.to_jpeg(0.9))          # 15552 x 10368 
cvp

But 9000 x 6000 crashes..

jmv38

@cvp thank for the info
i check again

jmv38

@cvp i had forgotten that 4000 is really 8000 because x2
i think 8000 should be enough for prints 60 cm wide => thanks. you saved my last week of coding!

However it is strange that 15000 is ok bu not 9000...?

Concerning the use of the project without any album, i could modify this for you if you are really interested in using this code. Are you?

thanks.

cvp

@jmv38 Not interested actually, thanks. If needed, I'll create an album.

cvp

@jmv38 During my tests and my multiple crashes, I'm pretty sure I have seen a red message during one microsecond, containing the words Decompression Bombs

This is a PIL message .
I didn't use PIL. Perhaps the ui.Image.save uses it...

Something like, it not sure, message disappeared immediately
/usr/lib64/python2.6/site-packages/PIL/Image.py:2261: DecompressionBombWarning: Image size (166109750 pixels) exceeds limit of 89478485 pixels, could be decompression bomb DOS attack. DecompressionBombWarning)

cvp

@jmv38 said:

However it is strange that 15000 is ok bu not 9000...?

15000 is in pixel, already multiplied by 2. The width was 7776, just < 9000

cvp

Same crash with

Image.warnings.simplefilter('error', Image.DecompressionBombWarning)
Image.MAX_IMAGE_PIXELS = 100000000000
jmv38

@cvp 5000 doesnt work for me and 5000<7000

cvp

@jmv38 and Sure that you don't have two pixels py point?
What is your device?

jmv38

@cvp i do have 2 pixels per point (ipad air)
i dont crash, it is just that the image saved is black
i checked that 4x1024 is ok and 4x1025 fails
i remember this ios limit 4096 from somwhere.

cvp

@jmv38 could you post your code, only this part, and the image you use?

jmv38

@cvp just to let you know here is my code.
It wont run because the rest of the code is missing, but it gives you the idea

def save(self):
    # make a hi resolution copy of back & images, then save it in camera roll
    xo, yo, w, h = self.page.back.frame
    c = self.page.back.background_color
    targetWidth = 4*1024
    s = targetWidth / w
    w, h = w*s, h*s
    page = ui.View( frame=(0,0,w,h), background_color=c)
    views = []
    for thumb in self.thumbs:
      x,y,w,h = thumb.frame
      x,y,w,h = (x-xo)*s, (y-yo)*s, w*s, h*s
      v = ui.View( frame=(x,y,w,h) )
      x,y,w,h = thumb.iv.frame
      x,y,w,h = x*s, y*s, w*s, h*s
      img = thumb.getImage(thumb.asset)
      iv = ui.ImageView(frame=(x,y,w,h), image=img)
      v.add_subview(iv)
      page.add_subview(v)
      views.append(v)
    # save page image in pythonista
    getTopView().add_subview(page)
    #page.bring_to_front()
    #if True: return
    path = 'temp.jpg'
    with ui.ImageContext(page.width, page.height) as ctx:
      page.draw_snapshot()                            
      ui_image = ctx.get_image()
    pil = Image.open(io.BytesIO(ui_image.to_png()))
    pil.save(path , quality=99)
    # save page image in albums
    asset = photos.create_image_asset(path)
    os.remove(path)
    getTopView().remove_subview(page)
    views = False
    console.hud_alert('saved')

looks like i must add the view to the screen to get the draw snapshot to work.

cvp

@jmv38 said:

looks like i must add the view to the screen to get the draw snapshot to work.

Not at all, I think. You've seen my little code, nothing goes to the screen

jmv38

@cvp you are correct. My pb was pbly sthg else.

cvp

@jmv38 The only 4096 limit I find for iPad Air is Max OpenGL Texture Sizes

cvp

From here

The UIImage documentation (as of iOS 10) no longer seems to mention size limitations, although if you use UIImageView with images whose dimensions are larger than the maximum texture size* supported by the device you happen to be using you do get very large memory consumption at render time.

(The memory consumption I see in Instruments seems to indicate that the entire image is put into a 32 bits per pixel buffer when the CA::Layer is rendered.)

If your app doesn't get kill by the OS due to memory usage, the UIImageView does still end up displaying the image though.

Given this, you'll still need strategies to deal with very large images.

  • You can check the maximum texture size using something like glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);. Just make sure you've set the EAGLContext current context to be something non-nil before querying OpenGL, otherwise you'll get zero.
cvp

I've tried in ObjectiveC, following this and this

and I have same crash with very big files of more than 25MB

# https://gist.github.com/jsbain/389a67c5aacb097b87fd
# https://github.com/tdamdouni/Pythonista/blob/master/_2017/core_image.py
import ui
from objc_util import *
import ctypes
import os

iv = ui.ImageView()
iv.image = ui.Image.named('P1020096.JPG') # 3888 x 2592 pixels
wi,hi = iv.image.size
iv.frame = (0,0,wi,hi)
print(wi,hi)                                                            # 3888 2592
w = 7000
h = w*hi/wi
print(w,h)                                                              # 7776 5184
with ui.ImageContext(w,h) as ctx:
    iv.draw_snapshot()
    ui_image = ctx.get_image()
uio = ObjCInstance(ui_image)
c.UIImageJPEGRepresentation.argtypes = [c_void_p, CGFloat]
c.UIImageJPEGRepresentation.restype = c_void_p
quality = 1.0
data = ObjCInstance(c.UIImageJPEGRepresentation(uio.ptr, quality))
filename = os.path.abspath('t1.jpg')
data.writeToFile_atomically_(filename, True)