luajitos

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

lens.lua (12357B)


      1 -- Lens - LuajitOS Image Viewer
      2 -- Displays BMP, PNG, and JPEG images with a toolbar
      3 
      4 if osprint then
      5     osprint("Lens: Starting...\n")
      6 end
      7 
      8 -- Check if required functions are available
      9 if not CRamdiskOpen or not BMPLoad or not ImageDraw then
     10     if osprint then
     11         osprint("Lens: ERROR - Required image functions not available\n")
     12     end
     13     return
     14 end
     15 
     16 -- Constants
     17 local TOOLBAR_HEIGHT = 30
     18 local BUTTON_HEIGHT = 24
     19 local BUTTON_WIDTH = 80
     20 local BUTTON_MARGIN = 3
     21 
     22 -- State variables (stored in window for proper scope)
     23 local state = {
     24     currentImage = nil,
     25     currentImageInfo = nil,
     26     currentImagePath = ""
     27 }
     28 
     29 -- Create main window
     30 local screenWidth = (sys and sys.screen and sys.screen[1] and sys.screen[1].width) or 1024
     31 local screenHeight = (sys and sys.screen and sys.screen[1] and sys.screen[1].height) or 768
     32 local windowWidth = 640
     33 local windowHeight = 480
     34 
     35 local window = app:newWindow(
     36     math.floor((screenWidth - windowWidth) / 2),
     37     math.floor((screenHeight - windowHeight) / 2),
     38     windowWidth,
     39     windowHeight
     40 )
     41 window.title = "Lens Viewer"
     42 window.resizable = true
     43 
     44 -- Helper: Load image from file path
     45 local function loadImage(path)
     46     if osprint then
     47         osprint("Lens: Loading image from: " .. path .. "\n")
     48     end
     49 
     50     -- Clean up previous image
     51     if state.currentImage and ImageDestroy then
     52         ImageDestroy(state.currentImage)
     53         state.currentImage = nil
     54     end
     55 
     56     -- Read file using SafeFS (fs) if available, otherwise CRamdisk
     57     local imageData = nil
     58 
     59     -- Try SafeFS first - it handles both ramdisk and diskfs paths
     60     if fs and fs.read then
     61         if osprint then
     62             osprint("Lens: Using SafeFS for path: " .. path .. "\n")
     63         end
     64         local content, err = fs:read(path)
     65         if content then
     66             imageData = content
     67         elseif osprint then
     68             osprint("Lens: SafeFS read failed: " .. tostring(err) .. "\n")
     69         end
     70     end
     71 
     72     -- Fall back to CRamdisk if SafeFS didn't work
     73     if not imageData and CRamdiskOpen then
     74         local handle, err = CRamdiskOpen(path, "r")
     75         if not handle then
     76             if osprint then
     77                 osprint("Lens: ERROR - Failed to open file: " .. tostring(err) .. "\n")
     78             end
     79             return false, "Failed to open file: " .. tostring(err)
     80         end
     81         imageData = CRamdiskRead(handle)
     82         CRamdiskClose(handle)
     83     end
     84 
     85     if not imageData then
     86         if osprint then
     87             osprint("Lens: ERROR - Failed to read file data\n")
     88         end
     89         return false, "Failed to read file"
     90     end
     91 
     92     if osprint then
     93         osprint("Lens: Read " .. #imageData .. " bytes\n")
     94 
     95         -- Show first 16 bytes as hex to identify file type
     96         local header = ""
     97         for i = 1, math.min(16, #imageData) do
     98             header = header .. string.format("%02X ", string.byte(imageData, i))
     99         end
    100         osprint("Lens: File header: " .. header .. "\n")
    101 
    102         -- Check for PNG magic number (89 50 4E 47) = "\x89PNG"
    103         if #imageData >= 4 then
    104             local b1, b2, b3, b4 = string.byte(imageData, 1, 4)
    105             if b1 == 0x89 and b2 == 0x50 and b3 == 0x4E and b4 == 0x47 then
    106                 osprint("Lens: File appears to be PNG format (valid magic number)\n")
    107             elseif b1 == 0x42 and b2 == 0x4D then
    108                 osprint("Lens: File appears to be BMP format\n")
    109             else
    110                 osprint("Lens: Unknown file format (invalid magic number)\n")
    111             end
    112         end
    113     end
    114 
    115     -- Check what decoders are available
    116     if osprint then
    117         osprint("Lens: PNGLoad available: " .. tostring(PNGLoad ~= nil) .. "\n")
    118         osprint("Lens: BMPLoad available: " .. tostring(BMPLoad ~= nil) .. "\n")
    119         osprint("Lens: JPEGLoad available: " .. tostring(JPEGLoad ~= nil) .. "\n")
    120     end
    121 
    122     -- Detect file type from magic bytes
    123     local fileType = nil
    124     if #imageData >= 4 then
    125         local b1, b2, b3, b4 = string.byte(imageData, 1, 4)
    126         if b1 == 0x89 and b2 == 0x50 and b3 == 0x4E and b4 == 0x47 then
    127             fileType = "PNG"
    128         elseif b1 == 0x42 and b2 == 0x4D then
    129             fileType = "BMP"
    130         elseif b1 == 0xFF and b2 == 0xD8 then
    131             fileType = "JPEG"
    132         end
    133     end
    134 
    135     if osprint then
    136         osprint("Lens: Detected file type: " .. tostring(fileType) .. "\n")
    137     end
    138 
    139     -- Try to decode based on detected type first, then try others
    140     local image = nil
    141     local decoder_used = nil
    142 
    143     -- Try detected type first
    144     if fileType == "PNG" and PNGLoad then
    145         if osprint then
    146             osprint("Lens: Trying PNG decoder...\n")
    147         end
    148         local result, err = PNGLoad(imageData)
    149         if result then
    150             image = result
    151             decoder_used = "PNG"
    152             if osprint then
    153                 osprint("Lens: Successfully decoded as PNG\n")
    154             end
    155         elseif osprint then
    156             osprint("Lens: PNG decoder failed: " .. tostring(err) .. "\n")
    157         end
    158     elseif fileType == "BMP" and BMPLoad then
    159         if osprint then
    160             osprint("Lens: Trying BMP decoder...\n")
    161         end
    162         local result, err = BMPLoad(imageData)
    163         if result then
    164             image = result
    165             decoder_used = "BMP"
    166             if osprint then
    167                 osprint("Lens: Successfully decoded as BMP\n")
    168             end
    169         elseif osprint then
    170             osprint("Lens: BMP decoder failed: " .. tostring(err) .. "\n")
    171         end
    172     elseif fileType == "JPEG" and JPEGLoad then
    173         if osprint then
    174             osprint("Lens: Trying JPEG decoder...\n")
    175         end
    176         local result, err = JPEGLoad(imageData)
    177         if result then
    178             image = result
    179             decoder_used = "JPEG"
    180             if osprint then
    181                 osprint("Lens: Successfully decoded as JPEG\n")
    182             end
    183         elseif osprint then
    184             osprint("Lens: JPEG decoder failed: " .. tostring(err) .. "\n")
    185         end
    186     end
    187 
    188     -- If detected type failed or unknown, try all decoders
    189     if not image and PNGLoad then
    190         local result, err = PNGLoad(imageData)
    191         if result then
    192             image = result
    193             decoder_used = "PNG"
    194         end
    195     end
    196 
    197     if not image and JPEGLoad then
    198         local result, err = JPEGLoad(imageData)
    199         if result then
    200             image = result
    201             decoder_used = "JPEG"
    202         end
    203     end
    204 
    205     if not image and BMPLoad then
    206         local result, err = BMPLoad(imageData)
    207         if result then
    208             image = result
    209             decoder_used = "BMP"
    210         end
    211     end
    212 
    213     if not image then
    214         if osprint then
    215             osprint("Lens: ERROR - Failed to decode image with any decoder\n")
    216         end
    217         return false, "Failed to decode image (not a valid BMP, PNG, or JPEG)"
    218     end
    219 
    220     -- Get image info
    221     local info = nil
    222     if ImageGetInfo then
    223         info = ImageGetInfo(image)
    224         if osprint and info then
    225             osprint("Lens: Dimensions: " .. info.width .. "x" .. info.height .. "\n")
    226         end
    227     end
    228 
    229     state.currentImage = image
    230     state.currentImageInfo = info
    231     state.currentImagePath = path
    232 
    233     window:markDirty()
    234     return true
    235 end
    236 
    237 -- Helper: Show open file dialog
    238 local function openFileDialog()
    239     if osprint then
    240         osprint("Lens: Opening file dialog...\n")
    241     end
    242 
    243     if not Dialog then
    244         if osprint then
    245             osprint("Lens: ERROR - Dialog library not available\n")
    246         end
    247         return
    248     end
    249 
    250     local dialog = Dialog.fileOpen("/", {
    251         app = app,
    252         fs = fs,  -- Use sandbox's fs (SafeFS instance)
    253         title = "Open Image",
    254         width = 500,
    255         height = 400
    256     })
    257 
    258     dialog:openDialog(function(path)
    259         if path then
    260             if osprint then
    261                 osprint("Lens: User selected: " .. path .. "\n")
    262             end
    263             local success, err = loadImage(path)
    264             if success then
    265                 if osprint then
    266                     osprint("Lens: Image loaded successfully\n")
    267                     osprint("Lens: state.currentImage = " .. tostring(state.currentImage) .. "\n")
    268                     osprint("Lens: state.currentImageInfo = " .. tostring(state.currentImageInfo) .. "\n")
    269                     if state.currentImageInfo then
    270                         osprint("Lens: Image dimensions: " .. state.currentImageInfo.width .. "x" .. state.currentImageInfo.height .. "\n")
    271                     end
    272                 end
    273                 -- Explicitly mark window dirty to force redraw
    274                 window:markDirty()
    275             else
    276                 if osprint then
    277                     osprint("Lens: Failed to load: " .. tostring(err) .. "\n")
    278                 end
    279             end
    280         else
    281             if osprint then
    282                 osprint("Lens: User cancelled dialog\n")
    283             end
    284         end
    285     end)
    286 end
    287 
    288 -- Draw callback
    289 window.onDraw = function(gfx)
    290     local winW = window:getWidth()
    291     local winH = window:getHeight()
    292 
    293     -- Draw toolbar background
    294     gfx:fillRect(0, 0, winW, TOOLBAR_HEIGHT, 0x3C3C3C)
    295 
    296     -- Draw "Open" button
    297     local btnX = BUTTON_MARGIN
    298     local btnY = BUTTON_MARGIN
    299     gfx:fillRect(btnX, btnY, BUTTON_WIDTH, BUTTON_HEIGHT, 0x0066CC)
    300     gfx:drawRect(btnX, btnY, BUTTON_WIDTH, BUTTON_HEIGHT, 0x0088EE)
    301     gfx:drawText(btnX + 24, btnY + 7, "Open", 0xFFFFFF)
    302 
    303     -- Draw separator line
    304     gfx:fillRect(0, TOOLBAR_HEIGHT, winW, 1, 0x555555)
    305 
    306     -- Image area
    307     local imageAreaY = TOOLBAR_HEIGHT + 1
    308     local imageAreaH = winH - imageAreaY
    309 
    310     -- Fill background with dark gray
    311     gfx:fillRect(0, imageAreaY, winW, imageAreaH, 0x2C2C2C)
    312 
    313     if state.currentImage and state.currentImageInfo then
    314         -- Calculate scaled dimensions to fit while maintaining aspect ratio
    315         local imgW = state.currentImageInfo.width
    316         local imgH = state.currentImageInfo.height
    317 
    318         local scaleX = winW / imgW
    319         local scaleY = imageAreaH / imgH
    320         local scale = math.min(scaleX, scaleY)
    321 
    322         -- Don't scale up, only scale down
    323         if scale > 1.0 then
    324             scale = 1.0
    325         end
    326 
    327         local scaledW = math.floor(imgW * scale)
    328         local scaledH = math.floor(imgH * scale)
    329 
    330         -- Center the image
    331         local x = math.floor((winW - scaledW) / 2)
    332         local y = imageAreaY + math.floor((imageAreaH - scaledH) / 2)
    333 
    334         -- Draw the image using gfx:drawImage (window-relative coordinates)
    335         if gfx.drawImage then
    336             gfx:drawImage(state.currentImage, x, y, scaledW, scaledH)
    337         end
    338 
    339         -- Draw image info
    340         local filename = state.currentImagePath:match("([^/]+)$") or state.currentImagePath
    341         local infoText = filename .. " (" .. imgW .. "x" .. imgH .. ")"
    342         gfx:drawText(5, winH - 15, infoText, 0xAAAAAA)
    343     else
    344         -- No image loaded
    345         local msg1 = "No image loaded"
    346         local msg2 = "Click 'Open' to select an image"
    347         gfx:drawText(winW / 2 - 60, winH / 2 - 10, msg1, 0x888888)
    348         gfx:drawText(winW / 2 - 100, winH / 2 + 10, msg2, 0x888888)
    349     end
    350 end
    351 
    352 -- Click handler
    353 window.onClick = function(mx, my)
    354     -- Check if click is on "Open" button
    355     local btnX = BUTTON_MARGIN
    356     local btnY = BUTTON_MARGIN
    357 
    358     if mx >= btnX and mx < btnX + BUTTON_WIDTH and
    359        my >= btnY and my < btnY + BUTTON_HEIGHT then
    360         openFileDialog()
    361         return
    362     end
    363 end
    364 
    365 -- Try to load image from command line argument or default
    366 local default_image = "/os/public/res/background.bmp"
    367 if args and #args > 0 and args[1] then
    368     local success, err = loadImage(args[1])
    369     if success then
    370         if osprint then
    371             osprint("Lens: Loaded initial image: " .. args[1] .. "\n")
    372         end
    373     else
    374         if osprint then
    375             osprint("Lens: Failed to load initial image: " .. tostring(err) .. "\n")
    376         end
    377     end
    378 else
    379     -- Load default image
    380     if osprint then
    381         osprint("Lens: No initial image specified, loading default: " .. default_image .. "\n")
    382     end
    383     local success, err = loadImage(default_image)
    384     if success then
    385         if osprint then
    386             osprint("Lens: Loaded default image successfully\n")
    387         end
    388     else
    389         if osprint then
    390             osprint("Lens: Failed to load default image: " .. tostring(err) .. "\n")
    391         end
    392     end
    393 end
    394 
    395 window:markDirty()