ElijahCrafter

Imageconvert

Nov 29th, 2025 (edited)
20
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.11 KB | Source Code | 0 0
  1. -- CC:Tweaked Image Downloader & Converter
  2. -- Downloads images from URLs and converts to displayable format
  3. -- Works entirely within CC:Tweaked!
  4.  
  5. local args = {...}
  6.  
  7. -- CC:Tweaked color palette
  8. local palette = {
  9.     {240, 240, 240, "0"}, -- white
  10.     {242, 178, 51, "1"},  -- orange
  11.     {229, 127, 216, "2"}, -- magenta
  12.     {153, 178, 242, "3"}, -- lightBlue
  13.     {222, 222, 108, "4"}, -- yellow
  14.     {127, 204, 25, "5"},  -- lime
  15.     {242, 178, 204, "6"}, -- pink
  16.     {76, 76, 76, "7"},    -- gray
  17.     {153, 153, 153, "8"}, -- lightGray
  18.     {76, 153, 178, "9"},  -- cyan
  19.     {178, 102, 229, "a"}, -- purple
  20.     {51, 102, 204, "b"},  -- blue
  21.     {127, 102, 76, "c"},  -- brown
  22.     {87, 166, 78, "d"},   -- green
  23.     {204, 76, 76, "e"},   -- red
  24.     {25, 25, 25, "f"},    -- black
  25. }
  26.  
  27. -- Find closest color
  28. local function findClosest(r, g, b)
  29.     local minDist = math.huge
  30.     local closest = "f"
  31.     for _, c in ipairs(palette) do
  32.         local dr = r - c[1]
  33.         local dg = g - c[2]
  34.         local db = b - c[3]
  35.         local dist = dr*dr + dg*dg + db*db
  36.         if dist < minDist then
  37.             minDist = dist
  38.             closest = c[4]
  39.         end
  40.     end
  41.     return closest
  42. end
  43.  
  44. -- Parse PPM P3 (ASCII) format
  45. local function parsePPM(data)
  46.     local lines = {}
  47.     for line in data:gmatch("[^\n]+") do
  48.         if not line:match("^#") then
  49.             table.insert(lines, line)
  50.         end
  51.     end
  52.    
  53.     local format = lines[1]
  54.     if format ~= "P3" then
  55.         return nil, "Not P3 PPM format"
  56.     end
  57.    
  58.     local dims = lines[2]:match("(%d+)%s+(%d+)")
  59.     local width, height = tonumber(lines[2]:match("^(%d+)")), tonumber(lines[2]:match("%s(%d+)"))
  60.     if not width or not height then
  61.         return nil, "Invalid dimensions"
  62.     end
  63.    
  64.     local maxVal = tonumber(lines[3])
  65.     if not maxVal then
  66.         return nil, "Invalid max value"
  67.     end
  68.    
  69.     -- Collect all pixel values
  70.     local values = {}
  71.     for i = 4, #lines do
  72.         for num in lines[i]:gmatch("%d+") do
  73.             table.insert(values, tonumber(num))
  74.         end
  75.     end
  76.    
  77.     -- Build pixel array
  78.     local pixels = {}
  79.     local idx = 1
  80.     for y = 1, height do
  81.         pixels[y] = {}
  82.         for x = 1, width do
  83.             local r = values[idx] or 0
  84.             local g = values[idx + 1] or 0
  85.             local b = values[idx + 2] or 0
  86.             -- Scale to 0-255 if needed
  87.             if maxVal ~= 255 then
  88.                 r = math.floor(r * 255 / maxVal)
  89.                 g = math.floor(g * 255 / maxVal)
  90.                 b = math.floor(b * 255 / maxVal)
  91.             end
  92.             pixels[y][x] = findClosest(r, g, b)
  93.             idx = idx + 3
  94.         end
  95.     end
  96.    
  97.     return {width = width, height = height, pixels = pixels}
  98. end
  99.  
  100. -- Parse simple BMP (24-bit uncompressed)
  101. local function parseBMP(data)
  102.     local function readInt(str, pos, bytes)
  103.         local val = 0
  104.         for i = 0, bytes - 1 do
  105.             val = val + (str:byte(pos + i) or 0) * (256 ^ i)
  106.         end
  107.         return val
  108.     end
  109.    
  110.     -- Check BMP signature
  111.     if data:sub(1, 2) ~= "BM" then
  112.         return nil, "Not a BMP file"
  113.     end
  114.    
  115.     local dataOffset = readInt(data, 11, 4)
  116.     local width = readInt(data, 19, 4)
  117.     local height = readInt(data, 23, 4)
  118.     local bpp = readInt(data, 29, 2)
  119.     local compression = readInt(data, 31, 4)
  120.    
  121.     if bpp ~= 24 or compression ~= 0 then
  122.         return nil, "Only 24-bit uncompressed BMP supported"
  123.     end
  124.    
  125.     local rowSize = math.floor((bpp * width + 31) / 32) * 4
  126.    
  127.     local pixels = {}
  128.     for y = 1, height do
  129.         pixels[y] = {}
  130.         local rowStart = dataOffset + (height - y) * rowSize
  131.         for x = 1, width do
  132.             local pixelStart = rowStart + (x - 1) * 3
  133.             local b = data:byte(pixelStart + 1) or 0
  134.             local g = data:byte(pixelStart + 2) or 0
  135.             local r = data:byte(pixelStart + 3) or 0
  136.             pixels[y][x] = findClosest(r, g, b)
  137.         end
  138.     end
  139.    
  140.     return {width = width, height = height, pixels = pixels}
  141. end
  142.  
  143. -- Resize image
  144. local function resizeImage(img, newWidth, newHeight)
  145.     if not img or not img.pixels or not img.width or not img.height then
  146.         return nil, "Invalid image data"
  147.     end
  148.    
  149.     local resized = {width = newWidth, height = newHeight, pixels = {}}
  150.     local xRatio = img.width / newWidth
  151.     local yRatio = img.height / newHeight
  152.    
  153.     for y = 1, newHeight do
  154.         resized.pixels[y] = {}
  155.         for x = 1, newWidth do
  156.             local srcX = math.floor((x - 0.5) * xRatio) + 1
  157.             local srcY = math.floor((y - 0.5) * yRatio) + 1
  158.             srcX = math.max(1, math.min(img.width, srcX))
  159.             srcY = math.max(1, math.min(img.height, srcY))
  160.             -- Safe access with fallback
  161.             local row = img.pixels[srcY]
  162.             if row then
  163.                 resized.pixels[y][x] = row[srcX] or "f"
  164.             else
  165.                 resized.pixels[y][x] = "f"
  166.             end
  167.         end
  168.     end
  169.    
  170.     return resized
  171. end
  172.  
  173. -- Save as NFP
  174. local function saveNFP(img, filename)
  175.     local file = fs.open(filename, "w")
  176.     if not file then
  177.         return false, "Cannot create file"
  178.     end
  179.    
  180.     for y = 1, img.height do
  181.         local line = ""
  182.         for x = 1, img.width do
  183.             line = line .. (img.pixels[y][x] or "f")
  184.         end
  185.         file.writeLine(line)
  186.     end
  187.     file.close()
  188.     return true
  189. end
  190.  
  191. -- Load NFP file
  192. local function loadNFP(filename)
  193.     if not fs.exists(filename) then
  194.         return nil, "File not found"
  195.     end
  196.    
  197.     local file = fs.open(filename, "r")
  198.     if not file then
  199.         return nil, "Cannot open file"
  200.     end
  201.    
  202.     local pixels = {}
  203.     local width = 0
  204.     local y = 1
  205.    
  206.     while true do
  207.         local line = file.readLine()
  208.         if not line then break end
  209.         pixels[y] = {}
  210.         width = math.max(width, #line)
  211.         for x = 1, #line do
  212.             pixels[y][x] = line:sub(x, x)
  213.         end
  214.         y = y + 1
  215.     end
  216.     file.close()
  217.    
  218.     return {width = width, height = y - 1, pixels = pixels}
  219. end
  220.  
  221. -- Color mapping for display
  222. local colorMap = {
  223.     ["0"] = colors.white,
  224.     ["1"] = colors.orange,
  225.     ["2"] = colors.magenta,
  226.     ["3"] = colors.lightBlue,
  227.     ["4"] = colors.yellow,
  228.     ["5"] = colors.lime,
  229.     ["6"] = colors.pink,
  230.     ["7"] = colors.gray,
  231.     ["8"] = colors.lightGray,
  232.     ["9"] = colors.cyan,
  233.     ["a"] = colors.purple,
  234.     ["b"] = colors.blue,
  235.     ["c"] = colors.brown,
  236.     ["d"] = colors.green,
  237.     ["e"] = colors.red,
  238.     ["f"] = colors.black,
  239. }
  240.  
  241. -- Display image
  242. local function displayImage(img, offsetX, offsetY)
  243.     offsetX = offsetX or 0
  244.     offsetY = offsetY or 0
  245.    
  246.     for y = 1, img.height do
  247.         for x = 1, img.width do
  248.             local char = img.pixels[y][x]
  249.             local color = colorMap[char:lower()] or colors.black
  250.             term.setCursorPos(x + offsetX, y + offsetY)
  251.             term.setBackgroundColor(color)
  252.             term.write(" ")
  253.         end
  254.     end
  255. end
  256.  
  257. -- Download and convert from URL
  258. local function downloadImage(url, filename, targetWidth, targetHeight)
  259.     print("Downloading image...")
  260.    
  261.     local response, err = http.get(url, nil, true) -- binary mode
  262.     if not response then
  263.         return nil, "Download failed: " .. (err or "unknown error")
  264.     end
  265.    
  266.     local data = response.readAll()
  267.     response.close()
  268.    
  269.     print("Downloaded " .. #data .. " bytes")
  270.    
  271.     -- Try to detect format
  272.     local img, parseErr
  273.    
  274.     if data:sub(1, 2) == "P3" then
  275.         print("Detected PPM format")
  276.         img, parseErr = parsePPM(data)
  277.     elseif data:sub(1, 2) == "BM" then
  278.         print("Detected BMP format")
  279.         img, parseErr = parseBMP(data)
  280.     else
  281.         return nil, "Unsupported format. Use PPM (P3) or BMP (24-bit)"
  282.     end
  283.    
  284.     if not img then
  285.         return nil, "Parse error: " .. (parseErr or "unknown")
  286.     end
  287.    
  288.     print("Image size: " .. img.width .. "x" .. img.height)
  289.    
  290.     -- Resize if needed
  291.     if targetWidth and targetHeight then
  292.         print("Resizing to " .. targetWidth .. "x" .. targetHeight)
  293.         local resized, resizeErr = resizeImage(img, targetWidth, targetHeight)
  294.         if not resized then
  295.             return nil, "Resize error: " .. (resizeErr or "unknown")
  296.         end
  297.         img = resized
  298.     end
  299.    
  300.     -- Save
  301.     if filename then
  302.         local ok, saveErr = saveNFP(img, filename)
  303.         if ok then
  304.             print("Saved to: " .. filename)
  305.         else
  306.             print("Save error: " .. saveErr)
  307.         end
  308.     end
  309.    
  310.     return img
  311. end
  312.  
  313. -- Interactive menu
  314. local function showMenu()
  315.     local w, h = term.getSize()
  316.    
  317.     while true do
  318.         term.setBackgroundColor(colors.black)
  319.         term.setTextColor(colors.white)
  320.         term.clear()
  321.         term.setCursorPos(1, 1)
  322.        
  323.         term.setTextColor(colors.yellow)
  324.         print("=== CC Image Converter ===")
  325.         term.setTextColor(colors.white)
  326.         print("")
  327.         print("1. Download from URL")
  328.         print("2. View saved image")
  329.         print("3. List saved images")
  330.         print("4. Convert URL to file")
  331.         print("5. Help")
  332.         print("6. Exit")
  333.         print("")
  334.         term.setTextColor(colors.gray)
  335.         print("Supports: PPM (P3), BMP (24-bit)")
  336.         term.setTextColor(colors.white)
  337.         print("")
  338.         write("Choice: ")
  339.        
  340.         local choice = read()
  341.        
  342.         if choice == "1" then
  343.             -- Download and display
  344.             print("")
  345.             write("Enter image URL: ")
  346.             local url = read()
  347.             if url and #url > 0 then
  348.                 local img, err = downloadImage(url, nil, w, h - 1)
  349.                 if img then
  350.                     term.clear()
  351.                     displayImage(img, 0, 0)
  352.                     term.setCursorPos(1, h)
  353.                     term.setBackgroundColor(colors.gray)
  354.                     term.setTextColor(colors.white)
  355.                     term.write(" Press any key ")
  356.                     os.pullEvent("key")
  357.                 else
  358.                     print("Error: " .. err)
  359.                     print("Press any key...")
  360.                     os.pullEvent("key")
  361.                 end
  362.             end
  363.            
  364.         elseif choice == "2" then
  365.             -- View saved image
  366.             print("")
  367.             write("Filename (.nfp): ")
  368.             local filename = read()
  369.             if filename and #filename > 0 then
  370.                 if not filename:match("%.nfp$") then
  371.                     filename = filename .. ".nfp"
  372.                 end
  373.                 local img, err = loadNFP(filename)
  374.                 if img then
  375.                     term.clear()
  376.                     displayImage(img, 0, 0)
  377.                     term.setCursorPos(1, h)
  378.                     term.setBackgroundColor(colors.gray)
  379.                     term.setTextColor(colors.white)
  380.                     term.write(" Press any key ")
  381.                     os.pullEvent("key")
  382.                 else
  383.                     print("Error: " .. err)
  384.                     print("Press any key...")
  385.                     os.pullEvent("key")
  386.                 end
  387.             end
  388.            
  389.         elseif choice == "3" then
  390.             -- List images
  391.             print("")
  392.             print("Saved images:")
  393.             local files = fs.list("/")
  394.             local found = false
  395.             for _, f in ipairs(files) do
  396.                 if f:match("%.nfp$") then
  397.                     print("  " .. f)
  398.                     found = true
  399.                 end
  400.             end
  401.             -- Check os/images folder too
  402.             if fs.exists("os/images") then
  403.                 local osFiles = fs.list("os/images")
  404.                 for _, f in ipairs(osFiles) do
  405.                     if f:match("%.nfp$") then
  406.                         print("  os/images/" .. f)
  407.                         found = true
  408.                     end
  409.                 end
  410.             end
  411.             if not found then
  412.                 print("  (no images found)")
  413.             end
  414.             print("")
  415.             print("Press any key...")
  416.             os.pullEvent("key")
  417.            
  418.         elseif choice == "4" then
  419.             -- Download and save
  420.             print("")
  421.             write("Enter image URL: ")
  422.             local url = read()
  423.             if url and #url > 0 then
  424.                 write("Save as (without .nfp): ")
  425.                 local filename = read()
  426.                 if filename and #filename > 0 then
  427.                     filename = filename .. ".nfp"
  428.                     write("Width (default " .. w .. "): ")
  429.                     local tw = tonumber(read()) or w
  430.                     write("Height (default " .. (h-1) .. "): ")
  431.                     local th = tonumber(read()) or (h - 1)
  432.                    
  433.                     local img, err = downloadImage(url, filename, tw, th)
  434.                     if not img then
  435.                         print("Error: " .. err)
  436.                     end
  437.                     print("")
  438.                     print("Press any key...")
  439.                     os.pullEvent("key")
  440.                 end
  441.             end
  442.            
  443.         elseif choice == "5" then
  444.             -- Help
  445.             term.clear()
  446.             term.setCursorPos(1, 1)
  447.             term.setTextColor(colors.yellow)
  448.             print("=== Help ===")
  449.             term.setTextColor(colors.white)
  450.             print("")
  451.             print("This program downloads images from")
  452.             print("the internet and displays them on")
  453.             print("your CC:Tweaked computer.")
  454.             print("")
  455.             term.setTextColor(colors.lime)
  456.             print("Supported formats:")
  457.             term.setTextColor(colors.white)
  458.             print("- PPM P3 (ASCII)")
  459.             print("- BMP 24-bit uncompressed")
  460.             print("")
  461.             term.setTextColor(colors.lime)
  462.             print("To convert images:")
  463.             term.setTextColor(colors.white)
  464.             print("1. Use online converter to make")
  465.             print("   PPM or BMP from your PNG/JPG")
  466.             print("2. Upload somewhere (pastebin, etc)")
  467.             print("3. Use URL to download here")
  468.             print("")
  469.             term.setTextColor(colors.lime)
  470.             print("Online converters:")
  471.             term.setTextColor(colors.cyan)
  472.             print("convertio.co, online-convert.com")
  473.             print("")
  474.             term.setTextColor(colors.white)
  475.             print("Press any key...")
  476.             os.pullEvent("key")
  477.            
  478.         elseif choice == "6" then
  479.             term.clear()
  480.             term.setCursorPos(1, 1)
  481.             return
  482.         end
  483.     end
  484. end
  485.  
  486. -- Command line usage
  487. if #args == 0 then
  488.     showMenu()
  489. elseif args[1] == "view" and args[2] then
  490.     local filename = args[2]
  491.     if not filename:match("%.nfp$") then
  492.         filename = filename .. ".nfp"
  493.     end
  494.     local img, err = loadNFP(filename)
  495.     if img then
  496.         term.clear()
  497.         displayImage(img, 0, 0)
  498.         local w, h = term.getSize()
  499.         term.setCursorPos(1, h)
  500.         term.setBackgroundColor(colors.gray)
  501.         term.write(" Press any key ")
  502.         os.pullEvent("key")
  503.         term.setBackgroundColor(colors.black)
  504.         term.clear()
  505.         term.setCursorPos(1, 1)
  506.     else
  507.         print("Error: " .. err)
  508.     end
  509. elseif args[1] == "get" and args[2] then
  510.     local url = args[2]
  511.     local filename = args[3]
  512.     local tw = tonumber(args[4]) or 51
  513.     local th = tonumber(args[5]) or 19
  514.     local img, err = downloadImage(url, filename, tw, th)
  515.     if not img then
  516.         print("Error: " .. err)
  517.     end
  518. elseif args[1] == "help" then
  519.     print("CC Image Converter")
  520.     print("")
  521.     print("Usage:")
  522.     print("  imgconv           - Interactive menu")
  523.     print("  imgconv view FILE - View saved image")
  524.     print("  imgconv get URL [FILE] [W] [H]")
  525.     print("                    - Download & convert")
  526.     print("  imgconv help      - Show this help")
  527.     print("")
  528.     print("Formats: PPM (P3), BMP (24-bit)")
  529. else
  530.     print("Unknown command. Use 'imgconv help'")
  531. end
  532.  
Advertisement
Add Comment
Please, Sign In to add comment