Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- CC:Tweaked Image Downloader & Converter
- -- Downloads images from URLs and converts to displayable format
- -- Works entirely within CC:Tweaked!
- local args = {...}
- -- CC:Tweaked color palette
- local palette = {
- {240, 240, 240, "0"}, -- white
- {242, 178, 51, "1"}, -- orange
- {229, 127, 216, "2"}, -- magenta
- {153, 178, 242, "3"}, -- lightBlue
- {222, 222, 108, "4"}, -- yellow
- {127, 204, 25, "5"}, -- lime
- {242, 178, 204, "6"}, -- pink
- {76, 76, 76, "7"}, -- gray
- {153, 153, 153, "8"}, -- lightGray
- {76, 153, 178, "9"}, -- cyan
- {178, 102, 229, "a"}, -- purple
- {51, 102, 204, "b"}, -- blue
- {127, 102, 76, "c"}, -- brown
- {87, 166, 78, "d"}, -- green
- {204, 76, 76, "e"}, -- red
- {25, 25, 25, "f"}, -- black
- }
- -- Find closest color
- local function findClosest(r, g, b)
- local minDist = math.huge
- local closest = "f"
- for _, c in ipairs(palette) do
- local dr = r - c[1]
- local dg = g - c[2]
- local db = b - c[3]
- local dist = dr*dr + dg*dg + db*db
- if dist < minDist then
- minDist = dist
- closest = c[4]
- end
- end
- return closest
- end
- -- Parse PPM P3 (ASCII) format
- local function parsePPM(data)
- local lines = {}
- for line in data:gmatch("[^\n]+") do
- if not line:match("^#") then
- table.insert(lines, line)
- end
- end
- local format = lines[1]
- if format ~= "P3" then
- return nil, "Not P3 PPM format"
- end
- local dims = lines[2]:match("(%d+)%s+(%d+)")
- local width, height = tonumber(lines[2]:match("^(%d+)")), tonumber(lines[2]:match("%s(%d+)"))
- if not width or not height then
- return nil, "Invalid dimensions"
- end
- local maxVal = tonumber(lines[3])
- if not maxVal then
- return nil, "Invalid max value"
- end
- -- Collect all pixel values
- local values = {}
- for i = 4, #lines do
- for num in lines[i]:gmatch("%d+") do
- table.insert(values, tonumber(num))
- end
- end
- -- Build pixel array
- local pixels = {}
- local idx = 1
- for y = 1, height do
- pixels[y] = {}
- for x = 1, width do
- local r = values[idx] or 0
- local g = values[idx + 1] or 0
- local b = values[idx + 2] or 0
- -- Scale to 0-255 if needed
- if maxVal ~= 255 then
- r = math.floor(r * 255 / maxVal)
- g = math.floor(g * 255 / maxVal)
- b = math.floor(b * 255 / maxVal)
- end
- pixels[y][x] = findClosest(r, g, b)
- idx = idx + 3
- end
- end
- return {width = width, height = height, pixels = pixels}
- end
- -- Parse simple BMP (24-bit uncompressed)
- local function parseBMP(data)
- local function readInt(str, pos, bytes)
- local val = 0
- for i = 0, bytes - 1 do
- val = val + (str:byte(pos + i) or 0) * (256 ^ i)
- end
- return val
- end
- -- Check BMP signature
- if data:sub(1, 2) ~= "BM" then
- return nil, "Not a BMP file"
- end
- local dataOffset = readInt(data, 11, 4)
- local width = readInt(data, 19, 4)
- local height = readInt(data, 23, 4)
- local bpp = readInt(data, 29, 2)
- local compression = readInt(data, 31, 4)
- if bpp ~= 24 or compression ~= 0 then
- return nil, "Only 24-bit uncompressed BMP supported"
- end
- local rowSize = math.floor((bpp * width + 31) / 32) * 4
- local pixels = {}
- for y = 1, height do
- pixels[y] = {}
- local rowStart = dataOffset + (height - y) * rowSize
- for x = 1, width do
- local pixelStart = rowStart + (x - 1) * 3
- local b = data:byte(pixelStart + 1) or 0
- local g = data:byte(pixelStart + 2) or 0
- local r = data:byte(pixelStart + 3) or 0
- pixels[y][x] = findClosest(r, g, b)
- end
- end
- return {width = width, height = height, pixels = pixels}
- end
- -- Resize image
- local function resizeImage(img, newWidth, newHeight)
- if not img or not img.pixels or not img.width or not img.height then
- return nil, "Invalid image data"
- end
- local resized = {width = newWidth, height = newHeight, pixels = {}}
- local xRatio = img.width / newWidth
- local yRatio = img.height / newHeight
- for y = 1, newHeight do
- resized.pixels[y] = {}
- for x = 1, newWidth do
- local srcX = math.floor((x - 0.5) * xRatio) + 1
- local srcY = math.floor((y - 0.5) * yRatio) + 1
- srcX = math.max(1, math.min(img.width, srcX))
- srcY = math.max(1, math.min(img.height, srcY))
- -- Safe access with fallback
- local row = img.pixels[srcY]
- if row then
- resized.pixels[y][x] = row[srcX] or "f"
- else
- resized.pixels[y][x] = "f"
- end
- end
- end
- return resized
- end
- -- Save as NFP
- local function saveNFP(img, filename)
- local file = fs.open(filename, "w")
- if not file then
- return false, "Cannot create file"
- end
- for y = 1, img.height do
- local line = ""
- for x = 1, img.width do
- line = line .. (img.pixels[y][x] or "f")
- end
- file.writeLine(line)
- end
- file.close()
- return true
- end
- -- Load NFP file
- local function loadNFP(filename)
- if not fs.exists(filename) then
- return nil, "File not found"
- end
- local file = fs.open(filename, "r")
- if not file then
- return nil, "Cannot open file"
- end
- local pixels = {}
- local width = 0
- local y = 1
- while true do
- local line = file.readLine()
- if not line then break end
- pixels[y] = {}
- width = math.max(width, #line)
- for x = 1, #line do
- pixels[y][x] = line:sub(x, x)
- end
- y = y + 1
- end
- file.close()
- return {width = width, height = y - 1, pixels = pixels}
- end
- -- Color mapping for display
- local colorMap = {
- ["0"] = colors.white,
- ["1"] = colors.orange,
- ["2"] = colors.magenta,
- ["3"] = colors.lightBlue,
- ["4"] = colors.yellow,
- ["5"] = colors.lime,
- ["6"] = colors.pink,
- ["7"] = colors.gray,
- ["8"] = colors.lightGray,
- ["9"] = colors.cyan,
- ["a"] = colors.purple,
- ["b"] = colors.blue,
- ["c"] = colors.brown,
- ["d"] = colors.green,
- ["e"] = colors.red,
- ["f"] = colors.black,
- }
- -- Display image
- local function displayImage(img, offsetX, offsetY)
- offsetX = offsetX or 0
- offsetY = offsetY or 0
- for y = 1, img.height do
- for x = 1, img.width do
- local char = img.pixels[y][x]
- local color = colorMap[char:lower()] or colors.black
- term.setCursorPos(x + offsetX, y + offsetY)
- term.setBackgroundColor(color)
- term.write(" ")
- end
- end
- end
- -- Download and convert from URL
- local function downloadImage(url, filename, targetWidth, targetHeight)
- print("Downloading image...")
- local response, err = http.get(url, nil, true) -- binary mode
- if not response then
- return nil, "Download failed: " .. (err or "unknown error")
- end
- local data = response.readAll()
- response.close()
- print("Downloaded " .. #data .. " bytes")
- -- Try to detect format
- local img, parseErr
- if data:sub(1, 2) == "P3" then
- print("Detected PPM format")
- img, parseErr = parsePPM(data)
- elseif data:sub(1, 2) == "BM" then
- print("Detected BMP format")
- img, parseErr = parseBMP(data)
- else
- return nil, "Unsupported format. Use PPM (P3) or BMP (24-bit)"
- end
- if not img then
- return nil, "Parse error: " .. (parseErr or "unknown")
- end
- print("Image size: " .. img.width .. "x" .. img.height)
- -- Resize if needed
- if targetWidth and targetHeight then
- print("Resizing to " .. targetWidth .. "x" .. targetHeight)
- local resized, resizeErr = resizeImage(img, targetWidth, targetHeight)
- if not resized then
- return nil, "Resize error: " .. (resizeErr or "unknown")
- end
- img = resized
- end
- -- Save
- if filename then
- local ok, saveErr = saveNFP(img, filename)
- if ok then
- print("Saved to: " .. filename)
- else
- print("Save error: " .. saveErr)
- end
- end
- return img
- end
- -- Interactive menu
- local function showMenu()
- local w, h = term.getSize()
- while true do
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.white)
- term.clear()
- term.setCursorPos(1, 1)
- term.setTextColor(colors.yellow)
- print("=== CC Image Converter ===")
- term.setTextColor(colors.white)
- print("")
- print("1. Download from URL")
- print("2. View saved image")
- print("3. List saved images")
- print("4. Convert URL to file")
- print("5. Help")
- print("6. Exit")
- print("")
- term.setTextColor(colors.gray)
- print("Supports: PPM (P3), BMP (24-bit)")
- term.setTextColor(colors.white)
- print("")
- write("Choice: ")
- local choice = read()
- if choice == "1" then
- -- Download and display
- print("")
- write("Enter image URL: ")
- local url = read()
- if url and #url > 0 then
- local img, err = downloadImage(url, nil, w, h - 1)
- if img then
- term.clear()
- displayImage(img, 0, 0)
- term.setCursorPos(1, h)
- term.setBackgroundColor(colors.gray)
- term.setTextColor(colors.white)
- term.write(" Press any key ")
- os.pullEvent("key")
- else
- print("Error: " .. err)
- print("Press any key...")
- os.pullEvent("key")
- end
- end
- elseif choice == "2" then
- -- View saved image
- print("")
- write("Filename (.nfp): ")
- local filename = read()
- if filename and #filename > 0 then
- if not filename:match("%.nfp$") then
- filename = filename .. ".nfp"
- end
- local img, err = loadNFP(filename)
- if img then
- term.clear()
- displayImage(img, 0, 0)
- term.setCursorPos(1, h)
- term.setBackgroundColor(colors.gray)
- term.setTextColor(colors.white)
- term.write(" Press any key ")
- os.pullEvent("key")
- else
- print("Error: " .. err)
- print("Press any key...")
- os.pullEvent("key")
- end
- end
- elseif choice == "3" then
- -- List images
- print("")
- print("Saved images:")
- local files = fs.list("/")
- local found = false
- for _, f in ipairs(files) do
- if f:match("%.nfp$") then
- print(" " .. f)
- found = true
- end
- end
- -- Check os/images folder too
- if fs.exists("os/images") then
- local osFiles = fs.list("os/images")
- for _, f in ipairs(osFiles) do
- if f:match("%.nfp$") then
- print(" os/images/" .. f)
- found = true
- end
- end
- end
- if not found then
- print(" (no images found)")
- end
- print("")
- print("Press any key...")
- os.pullEvent("key")
- elseif choice == "4" then
- -- Download and save
- print("")
- write("Enter image URL: ")
- local url = read()
- if url and #url > 0 then
- write("Save as (without .nfp): ")
- local filename = read()
- if filename and #filename > 0 then
- filename = filename .. ".nfp"
- write("Width (default " .. w .. "): ")
- local tw = tonumber(read()) or w
- write("Height (default " .. (h-1) .. "): ")
- local th = tonumber(read()) or (h - 1)
- local img, err = downloadImage(url, filename, tw, th)
- if not img then
- print("Error: " .. err)
- end
- print("")
- print("Press any key...")
- os.pullEvent("key")
- end
- end
- elseif choice == "5" then
- -- Help
- term.clear()
- term.setCursorPos(1, 1)
- term.setTextColor(colors.yellow)
- print("=== Help ===")
- term.setTextColor(colors.white)
- print("")
- print("This program downloads images from")
- print("the internet and displays them on")
- print("your CC:Tweaked computer.")
- print("")
- term.setTextColor(colors.lime)
- print("Supported formats:")
- term.setTextColor(colors.white)
- print("- PPM P3 (ASCII)")
- print("- BMP 24-bit uncompressed")
- print("")
- term.setTextColor(colors.lime)
- print("To convert images:")
- term.setTextColor(colors.white)
- print("1. Use online converter to make")
- print(" PPM or BMP from your PNG/JPG")
- print("2. Upload somewhere (pastebin, etc)")
- print("3. Use URL to download here")
- print("")
- term.setTextColor(colors.lime)
- print("Online converters:")
- term.setTextColor(colors.cyan)
- print("convertio.co, online-convert.com")
- print("")
- term.setTextColor(colors.white)
- print("Press any key...")
- os.pullEvent("key")
- elseif choice == "6" then
- term.clear()
- term.setCursorPos(1, 1)
- return
- end
- end
- end
- -- Command line usage
- if #args == 0 then
- showMenu()
- elseif args[1] == "view" and args[2] then
- local filename = args[2]
- if not filename:match("%.nfp$") then
- filename = filename .. ".nfp"
- end
- local img, err = loadNFP(filename)
- if img then
- term.clear()
- displayImage(img, 0, 0)
- local w, h = term.getSize()
- term.setCursorPos(1, h)
- term.setBackgroundColor(colors.gray)
- term.write(" Press any key ")
- os.pullEvent("key")
- term.setBackgroundColor(colors.black)
- term.clear()
- term.setCursorPos(1, 1)
- else
- print("Error: " .. err)
- end
- elseif args[1] == "get" and args[2] then
- local url = args[2]
- local filename = args[3]
- local tw = tonumber(args[4]) or 51
- local th = tonumber(args[5]) or 19
- local img, err = downloadImage(url, filename, tw, th)
- if not img then
- print("Error: " .. err)
- end
- elseif args[1] == "help" then
- print("CC Image Converter")
- print("")
- print("Usage:")
- print(" imgconv - Interactive menu")
- print(" imgconv view FILE - View saved image")
- print(" imgconv get URL [FILE] [W] [H]")
- print(" - Download & convert")
- print(" imgconv help - Show this help")
- print("")
- print("Formats: PPM (P3), BMP (24-bit)")
- else
- print("Unknown command. Use 'imgconv help'")
- end
Advertisement
Add Comment
Please, Sign In to add comment