nonogamer9

CC-BMP : An BMP Image Viewer For ComputerCraft

Jan 24th, 2025 (edited)
374
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 12.44 KB | Software | 0 0
  1. local function readBMP(filename)
  2.     local file = fs.open(filename, "rb")
  3.     if not file then
  4.         return nil, "Failed to open file"
  5.     end
  6.     local header = file.read(54)
  7.     if #header < 54 or string.sub(header, 1, 2) ~= "BM" then
  8.         file.close()
  9.         return nil, "Not a valid BMP file"
  10.     end
  11.     local bpp = string.byte(header, 29)
  12.     if bpp ~= 24 then
  13.         file.close()
  14.         return nil, "Only 24-bit BMPs supported"
  15.     end
  16.     local width = string.byte(header, 19) + string.byte(header, 20) * 256
  17.         + string.byte(header, 21) * 65536 + string.byte(header, 22) * 16777216
  18.        
  19.     local height = string.byte(header, 23) + string.byte(header, 24) * 256
  20.         + string.byte(header, 25) * 65536 + string.byte(header, 26) * 16777216
  21.     local rowSize = width * 3
  22.     local padding = (4 - (rowSize % 4)) % 4
  23.     local pixels = {}
  24.     for y = height, 1, -1 do
  25.         pixels[y] = {}
  26.         for x = 1, width do
  27.             local b = file.read(1)
  28.             local g = file.read(1)
  29.             local r = file.read(1)
  30.             pixels[y][x] = {
  31.                 r = string.byte(r),
  32.                 g = string.byte(g),
  33.                 b = string.byte(b)
  34.             }
  35.         end
  36.         file.read(padding)
  37.        
  38.         if y % 10 == 0 then
  39.             os.queueEvent("yield")
  40.             os.pullEvent("yield")
  41.         end
  42.     end
  43.     file.close()
  44.     return {width = width, height = height, pixels = pixels}
  45. end
  46.  
  47. local function resizeImage(image, newWidth, newHeight)
  48.   local resized = {width = newWidth, height = newHeight, pixels = {}}
  49.   for y = 1, newHeight do
  50.     resized.pixels[y] = {}
  51.     for x = 1, newWidth do
  52.       local srcX = math.floor(x * image.width / newWidth)
  53.       local srcY = math.floor(y * image.height / newHeight)
  54.       resized.pixels[y][x] = image.pixels[srcY][srcX]
  55.     end
  56.     if y % 10 == 0 then
  57.       os.queueEvent("yield")
  58.       os.pullEvent("yield")
  59.     end
  60.   end
  61.   return resized
  62. end
  63.  
  64. local colors = {
  65.   [0x1] = {240, 240, 240}, [0x2] = {242, 178, 51}, [0x4] = {229, 127, 216},
  66.   [0x8] = {153, 178, 242}, [0x10] = {222, 222, 108}, [0x20] = {127, 204, 25},
  67.   [0x40] = {242, 178, 204}, [0x80] = {76, 76, 76}, [0x100] = {153, 153, 153},
  68.   [0x200] = {76, 153, 178}, [0x400] = {178, 102, 229}, [0x800] = {51, 102, 204},
  69.   [0x1000] = {127, 102, 76}, [0x2000] = {87, 166, 78}, [0x4000] = {204, 76, 76},
  70.   [0x8000] = {17, 17, 17}
  71. }
  72.  
  73. local function rgbToComputerCraftColor(r, g, b)
  74.   local closestColor = 0x1
  75.   local minDifference = math.huge
  76.   for code, rgb in pairs(colors) do
  77.     local difference = math.sqrt((r - rgb[1])^2 + (g - rgb[2])^2 + (b - rgb[3])^2)
  78.     if difference < minDifference then
  79.       minDifference = difference
  80.       closestColor = code
  81.     end
  82.   end
  83.   return closestColor
  84. end
  85.  
  86. local function displayBMP(image, output)
  87.   local width, height = output.getSize()
  88.   local resizedImage = resizeImage(image, width, height)
  89.   for y = 1, height do
  90.     for x = 1, width do
  91.       local pixel = resizedImage.pixels[y][x]
  92.       local color = rgbToComputerCraftColor(pixel.r, pixel.g, pixel.b)
  93.       output.setCursorPos(x, y)
  94.       output.setBackgroundColor(color)
  95.       output.write(" ")
  96.     end
  97.     if y % 5 == 0 then
  98.       os.queueEvent("yield")
  99.       os.pullEvent("yield")
  100.     end
  101.   end
  102. end
  103.  
  104. local function displayBMPMode2(image, output)
  105.   local width, height = output.getSize()
  106.   local resizedImage = resizeImage(image, width * 2, height * 2)
  107.   local charSet = {" ", "\131", "\132", "\133", "\135", "\136", "\137", "\138", "\139", "\140"}
  108.   local function getNearestColor(r, g, b)
  109.     local closestColor = 0x1
  110.     local minDifference = math.huge
  111.     for code, rgb in pairs(colors) do
  112.       local difference = math.abs(r - rgb[1]) + math.abs(g - rgb[2]) + math.abs(b - rgb[3])
  113.       if difference < minDifference then
  114.         minDifference = difference
  115.         closestColor = code
  116.       end
  117.     end
  118.     return closestColor
  119.   end
  120.   local function blendColors(c1, c2)
  121.     return {
  122.       r = (c1.r + c2.r) / 2,
  123.       g = (c1.g + c2.g) / 2,
  124.       b = (c1.b + c2.b) / 2
  125.     }
  126.   end
  127.   for y = 1, height do
  128.     for x = 1, width do
  129.       local tl = resizedImage.pixels[y*2-1][x*2-1]
  130.       local tr = resizedImage.pixels[y*2-1][x*2]
  131.       local bl = resizedImage.pixels[y*2][x*2-1]
  132.       local br = resizedImage.pixels[y*2][x*2]
  133.       local avgColor = blendColors(blendColors(tl, tr), blendColors(bl, br))
  134.       local intensity = (avgColor.r + avgColor.g + avgColor.b) / 3
  135.       local pattern = math.floor(intensity / 28.4) + 1
  136.       local bgColor = getNearestColor(tl.r, tl.g, tl.b)
  137.       local fgColor = getNearestColor(br.r, br.g, br.b)
  138.       if bgColor == fgColor then
  139.         if pattern > 5 then
  140.           fgColor = getNearestColor(math.min(br.r + 30, 255), math.min(br.g + 30, 255), math.min(br.b + 30, 255))
  141.         else
  142.           bgColor = getNearestColor(math.max(tl.r - 30, 0), math.max(tl.g - 30, 0), math.max(tl.b - 30, 0))
  143.         end
  144.       end
  145.       output.setCursorPos(x, y)
  146.       output.setBackgroundColor(bgColor)
  147.       output.setTextColor(fgColor)
  148.       output.write(charSet[pattern])
  149.     end
  150.     if y % 5 == 0 then
  151.       os.queueEvent("yield")
  152.       os.pullEvent("yield")
  153.     end
  154.   end
  155. end
  156.  
  157. local function displayBMPCraftOSPC(image, output, colorMode)
  158.   local screenWidth, screenHeight = output.getSize()
  159.   local imageWidth, imageHeight = image.width, image.height
  160.   local originalPalette = {}
  161.   for i = 0, 255 do
  162.     originalPalette[i] = {term.getPaletteColor(i)}
  163.   end
  164.   local originalGraphicsMode = term.getGraphicsMode()
  165.   local originalBackgroundColor = term.getBackgroundColor()
  166.   local originalTextColor = term.getTextColor()
  167.  
  168.   -- Setup palette for 256-color mode if needed
  169.   if colorMode == 2 or colorMode == 3 then
  170.     term.setGraphicsMode(2)
  171.     local cubeSize = 5  -- Palette steps for RGB cube
  172.     for i = 16, 255 do
  173.       local index = i - 16
  174.       local rIndex = math.floor(index / (cubeSize * cubeSize))
  175.       local gIndex = math.floor((index % (cubeSize * cubeSize)) / cubeSize)
  176.       local bIndex = index % cubeSize
  177.       local r = math.floor(rIndex * 255 / (cubeSize - 1))
  178.       local g = math.floor(gIndex * 255 / (cubeSize - 1))
  179.       local b = math.floor(bIndex * 255 / (cubeSize - 1))
  180.       term.setPaletteColor(i, r / 255, g / 255, b / 255)
  181.     end
  182.   else
  183.     term.setGraphicsMode(colorMode)
  184.   end
  185.  
  186.   local function adjustBrightness(value)
  187.     return math.min(255, math.max(0, math.floor(value * 1.2)))  -- Increase brightness by 20%
  188.   end
  189.  
  190.   local function mapColor(r, g, b)
  191.     r, g, b = adjustBrightness(r), adjustBrightness(g), adjustBrightness(b)
  192.     local cubeSize = 5
  193.     local rIndex = math.floor(r / (255 / (cubeSize - 1)))
  194.     local gIndex = math.floor(g / (255 / (cubeSize - 1)))
  195.     local bIndex = math.floor(b / (255 / (cubeSize - 1)))
  196.     return 16 + rIndex * cubeSize * cubeSize + gIndex * cubeSize + bIndex
  197.   end
  198.  
  199.   if colorMode == 3 then
  200.     -- Dithered 256-color mode (new mode)
  201.     local width = screenWidth * 6
  202.     local height = screenHeight * 9
  203.     local resizedImage = resizeImage(image, width, height)
  204.  
  205.     -- Convert pixels to floats for error diffusion
  206.     local buffer = {}
  207.     for y = 1, height do
  208.       buffer[y] = {}
  209.       for x = 1, width do
  210.         buffer[y][x] = {
  211.           r = resizedImage.pixels[y][x].r,
  212.           g = resizedImage.pixels[y][x].g,
  213.           b = resizedImage.pixels[y][x].b
  214.         }
  215.       end
  216.     end
  217.  
  218.     local function paletteColor(index)
  219.       local r, g, b = term.getPaletteColor(index)
  220.       return {r = r * 255, g = g * 255, b = b * 255}
  221.     end
  222.  
  223.     for y = 1, height do
  224.       for x = 1, width do
  225.         local oldPixel = buffer[y][x]
  226.         local newIndex = mapColor(oldPixel.r, oldPixel.g, oldPixel.b)
  227.         local newPixel = paletteColor(newIndex)
  228.         output.setPixel(x - 1, y - 1, newIndex) -- 0-based coordinates
  229.  
  230.         local err = {
  231.           r = oldPixel.r - newPixel.r,
  232.           g = oldPixel.g - newPixel.g,
  233.           b = oldPixel.b - newPixel.b
  234.         }
  235.  
  236.         if x + 1 <= width then
  237.           buffer[y][x + 1].r = buffer[y][x + 1].r + err.r * 7 / 16
  238.           buffer[y][x + 1].g = buffer[y][x + 1].g + err.g * 7 / 16
  239.           buffer[y][x + 1].b = buffer[y][x + 1].b + err.b * 7 / 16
  240.         end
  241.         if x - 1 >= 1 and y + 1 <= height then
  242.           buffer[y + 1][x - 1].r = buffer[y + 1][x - 1].r + err.r * 3 / 16
  243.           buffer[y + 1][x - 1].g = buffer[y + 1][x - 1].g + err.g * 3 / 16
  244.           buffer[y + 1][x - 1].b = buffer[y + 1][x - 1].b + err.b * 3 / 16
  245.         end
  246.         if y + 1 <= height then
  247.           buffer[y + 1][x].r = buffer[y + 1][x].r + err.r * 5 / 16
  248.           buffer[y + 1][x].g = buffer[y + 1][x].g + err.g * 5 / 16
  249.           buffer[y + 1][x].b = buffer[y + 1][x].b + err.b * 5 / 16
  250.         end
  251.         if x + 1 <= width and y + 1 <= height then
  252.           buffer[y + 1][x + 1].r = buffer[y + 1][x + 1].r + err.r * 1 / 16
  253.           buffer[y + 1][x + 1].g = buffer[y + 1][x + 1].g + err.g * 1 / 16
  254.           buffer[y + 1][x + 1].b = buffer[y + 1][x + 1].b + err.b * 1 / 16
  255.         end
  256.       end
  257.       if y % 10 == 0 then
  258.         os.queueEvent("yield")
  259.         os.pullEvent("yield")
  260.       end
  261.     end
  262.  
  263.   else
  264.     -- Normal 16 or 256 color mode
  265.     local width = screenWidth * 6
  266.     local height = screenHeight * 9
  267.     local resizedImage = resizeImage(image, width, height)
  268.     for y = 0, height - 1 do
  269.       for x = 0, width - 1 do
  270.         local sourceX = math.floor(x * image.width / width)
  271.         local sourceY = math.floor(y * image.height / height)
  272.         local pixel = image.pixels[sourceY + 1][sourceX + 1]
  273.         local color = mapColor(pixel.r, pixel.g, pixel.b)
  274.         output.setPixel(x, y, color)
  275.       end
  276.       if y % 10 == 0 then
  277.         os.queueEvent("yield")
  278.         os.pullEvent("yield")
  279.       end
  280.     end
  281.   end
  282.  
  283.   print("Image displayed. Press any key to exit.")
  284.   os.pullEvent("key")
  285.   term.setGraphicsMode(originalGraphicsMode)
  286.   for i = 0, 255 do
  287.     term.setPaletteColor(i, table.unpack(originalPalette[i]))
  288.   end
  289.   term.setBackgroundColor(originalBackgroundColor)
  290.   term.setTextColor(originalTextColor)
  291.   term.setBackgroundColor(colors.black)
  292.   term.setTextColor(colors.white)
  293.   term.clear()
  294.   term.setCursorPos(1, 1)
  295. end
  296.  
  297. local function downloadBMP(url, filename)
  298.   if not http then
  299.     error("HTTP API is not enabled. Enable it in ComputerCraft.cfg")
  300.   end
  301.   print("Downloading " .. url .. "...")
  302.   local response = http.get(url, nil, true)
  303.   if not response then
  304.     error("Failed to download file")
  305.   end
  306.   local file = fs.open(filename, "wb")
  307.   local content = response.readAll()
  308.   for i = 1, #content, 8192 do
  309.     file.write(content:sub(i, i + 8191))
  310.     os.queueEvent("yield")
  311.     os.pullEvent("yield")
  312.   end
  313.   file.close()
  314.   response.close()
  315.   print("Downloaded as " .. filename)
  316. end
  317.  
  318. print("CC-BMP : An Enhanced BMP Image Viewer For ComputerCraft Coded By nonogamer9!")
  319. print("Enter a local filename or URL:")
  320. local input = read()
  321. local filename = input
  322. if string.sub(input, 1, 4) == "http" then
  323.   filename = "downloaded.bmp"
  324.   downloadBMP(input, filename)
  325. end
  326. local image, err = readBMP(filename)
  327. if not image then
  328.   print("Error reading BMP file:", err)
  329.   return
  330. end
  331. print("Select display mode:")
  332. print("1. Basic ComputerCraft screen")
  333. print("2. Mode 2 (Just a better resolution.)")
  334. print("3. CraftOS-PC 16-color Graphics Mode")
  335. print("4. CraftOS-PC 256-color Graphics Mode")
  336. print("5. Monitor")
  337. print("6. CraftOS-PC 256-color Dithered Mode") -- New mode
  338. local mode = tonumber(read())
  339. local output
  340. if mode == 5 then
  341.   output = peripheral.find("monitor")
  342.   if not output then
  343.     print("No monitor found")
  344.     return
  345.   end
  346. else
  347.   output = term
  348. end
  349.  
  350. if mode == 1 then
  351.   displayBMP(image, output)
  352. elseif mode == 2 then
  353.   displayBMPMode2(image, output)
  354. elseif mode == 3 then
  355.   if term.setGraphicsMode then
  356.     displayBMPCraftOSPC(image, output, 1)  -- 16-color mode
  357.   else
  358.     print("CraftOS-PC Graphics Mode not supported")
  359.   end
  360. elseif mode == 4 then
  361.   if term.setGraphicsMode then
  362.     displayBMPCraftOSPC(image, output, 2)  -- 256-color mode
  363.   else
  364.     print("CraftOS-PC Graphics Mode not supported")
  365.   end
  366. elseif mode == 6 then
  367.   if term.setGraphicsMode then
  368.     displayBMPCraftOSPC(image, output, 3)  -- Our new dithered 256-color mode
  369.   else
  370.     print("CraftOS-PC Graphics Mode not supported")
  371.   end
  372. elseif mode == 5 then
  373.   displayBMP(image, output)
  374. else
  375.   print("Invalid mode selected")
  376. end
  377. print("Image displayed")
Advertisement
Add Comment
Please, Sign In to add comment