Turn HEIC into a Codea image

So here’s a very specific piece of code for a very specific situation that most yall will probably never need, but if you do, here it is.

This is pretty much only necessary if you need to open an HEIC image file from the document picker, because in that situation they don’t convert properly into Codea images.

It’s a two-step process.

First it turns an NSURL for the image into a CGImage in the image layer of a UIView. This can be done anywhere in code.

Then it uses UIView’s drawViewHierarchyInRect_afterScreenUpdates_ function to draw that layer to a proper Codea image. This must be called from inside draw() or it will return black.

-- Call from ObjC delegate (any thread).
-- Returns a UIView with the image in its image layer, parked offscreen.
function UIViewFromImageNSURL(oURL)
  local data   = objc.NSData:dataWithContentsOfURL_(oURL)
  local oCIImg = objc.CIImage:imageWithData_(data)
  local oExt   = oCIImg.extent
  local oCGImg = objc.CIContext:context_():createCGImage_fromRect_(oCIImg, oExt)
  
  local nW     = oExt.size.width
  local nH     = oExt.size.height
  local nScale = math.min(WIDTH/nW, HEIGHT/nH)
  local nOutW  = math.floor(nW * nScale)
  local nOutH  = math.floor(nH * nScale)
  
  local oView = objc.UIView:alloc()
  :initWithFrame_(objc.rect(-nOutW, -nOutH, nOutW, nOutH))
  oView.layer.contents        = oCGImg
  oView.layer.contentsGravity = "resize"
  objc.viewer.view:addSubview_(oView)
  return oView
end

-- MUST be called from draw().
-- Snapshots the view into a Codea image, removes the view, returns the image.
function CodeaImageFromUIViewImageLayer(oView)
  local nW   = math.floor(oView.frame.size.width)
  local nH   = math.floor(oView.frame.size.height)
  local oImg = objc.UIGraphicsImageRenderer:alloc()
  :initWithBounds_(objc.rect(0, 0, nW, nH))
  :imageWithActions_(function(oRCtx)
    oView:drawViewHierarchyInRect_afterScreenUpdates_(
    objc.rect(0, 0, nW, nH), true)
  end)
  oView:removeFromSuperview_()
  return oImg
end

function setup()
  viewer.mode = STANDARD
  pickedImg   = nil
  oSnapView   = nil
  pendingSnap = false
  info        = "tap to pick"
  
  local Del = objc.delegate("UIDocumentPickerDelegate")
  
  function Del:documentPicker_didPickDocumentsAtURLs_(oPicker, oURLs)
    oSnapView   = UIViewFromImageNSURL(oURLs[1])
    pendingSnap = true
    info        = "loading..."
  end
  
  function Del:documentPickerWasCancelled_(oPicker)
    info = "cancelled"
  end
  
  del = Del()
end

function draw()
  background(40)
  
  if pendingSnap and oSnapView then
    pendingSnap = false
    pickedImg   = CodeaImageFromUIViewImageLayer(oSnapView)
    oSnapView   = nil
    info        = pickedImg.width .. "x" .. pickedImg.height
    print("result:", tostring(pickedImg), pickedImg.width, pickedImg.height)
  end
  
  if pickedImg then
    local nS = math.min(WIDTH*0.9/pickedImg.width, HEIGHT*0.7/pickedImg.height)
    sprite(pickedImg, WIDTH/2, HEIGHT/2+60, pickedImg.width*nS, pickedImg.height*nS)
  end
  
  fill(255); fontSize(16); textAlign(CENTER); textMode(CENTER)
  text(info, WIDTH/2, 60)
end

function touched(t)
  if t.state ~= BEGAN then return end
  local types = {"public.jpeg","public.heic","public.image"}
  local doc = objc.UIDocumentPickerViewController
  :alloc():initWithDocumentTypes_inMode_(types, 0)
  doc.delegate = del
  objc.viewer:presentModalViewController_animated_(doc, true)
end

…easy peasy lemon squeezy!

Except actually difficult difficult lemon difficult. :sweat_smile:

1 Like