nonogamer9

OC-BMP : An BMP Image Viewer For OpenComputers

Jan 24th, 2025 (edited)
290
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 34.28 KB | Software | 0 0
  1. local component = require("component")
  2. local event = require("event")
  3. local term = require("term")
  4. local gpu = component.gpu
  5. local internet = require("internet")
  6. local computer = require("computer")
  7. local unicode = require("unicode")
  8. local bit32 = require("bit32")
  9. local filesystem = require("filesystem")
  10.  
  11. local function stringUnpack(format, str, pos)
  12.   local function unpackInt(str, pos, size, signed)
  13.     local val = 0
  14.     for i = 0, size - 1 do
  15.       val = val + str:byte(pos + i) * (256 ^ i)
  16.     end
  17.     if signed and val >= 2^(size*8-1) then
  18.       val = val - 2^(size*8)
  19.     end
  20.     return val, pos + size
  21.   end
  22.   if format == "<I4" then
  23.     return unpackInt(str, pos, 4, false)
  24.   elseif format == "<I2" then
  25.     return unpackInt(str, pos, 2, false)
  26.   elseif format == "BBB" then
  27.     return str:byte(pos), str:byte(pos+1), str:byte(pos+2), pos+3
  28.   else
  29.     error("Unsupported format: " .. format)
  30.   end
  31. end
  32. local function yieldIfNeeded()
  33.   if computer.uptime() % 0.1 < 0.01 then
  34.     os.sleep(0)
  35.   end
  36. end
  37.  
  38.  
  39. local function downloadImage(url)
  40.   local maxAttempts = 3
  41.   local attempt = 0
  42.   local data = ""
  43.   while attempt < maxAttempts do
  44.     attempt = attempt + 1
  45.     local response = internet.request(url)
  46.    
  47.     if response then
  48.       for chunk in response do
  49.         data = data .. chunk
  50.         yieldIfNeeded()
  51.       end
  52.      
  53.       if data ~= "" then
  54.         return data
  55.       end
  56.     end
  57.    
  58.     print("Attempt " .. attempt .. " failed. Retrying...")
  59.     os.sleep(2)
  60.   end
  61.   error("Failed to download image after " .. maxAttempts .. " attempts")
  62. end
  63.  
  64. local function loadImage(path)
  65.   -- Check if input is URL or local path
  66.   if path:match("^https?://") then
  67.     return downloadImage(path)
  68.   else
  69.     local fullPath = "/home/" .. path
  70.     if filesystem.exists(fullPath) then
  71.       local file = io.open(fullPath, "rb")
  72.       if file then
  73.         local content = file:read("*a")
  74.         file:close()
  75.         return content
  76.       else
  77.         error("Could not open file: " .. fullPath)
  78.       end
  79.     else
  80.       error("File does not exist: " .. fullPath)
  81.     end
  82.   end
  83. end
  84.  
  85. -- Mode 1 specific functions
  86. local function decodeBMPMode1(imageData)
  87.   local width = stringUnpack("<I4", imageData, 19)
  88.   local height = stringUnpack("<I4", imageData, 23)
  89.   local bitsPerPixel = stringUnpack("<I2", imageData, 29)
  90.   local compression = stringUnpack("<I4", imageData, 31)
  91.   local dataOffset = stringUnpack("<I4", imageData, 11)
  92.   if bitsPerPixel ~= 24 or compression ~= 0 then
  93.     error("Unsupported BMP format. Only 24-bit uncompressed BMPs are supported.")
  94.   end
  95.   local decodedImage = {}
  96.   local rowSize = math.floor((bitsPerPixel * width + 31) / 32) * 4
  97.   local xRatio = width / 160
  98.   local yRatio = height / 50
  99.   for y = 0, 49 do
  100.     for x = 0, 159 do
  101.       local srcX = math.floor(x * xRatio)
  102.       local srcY = math.floor(y * yRatio)
  103.       local rowStart = dataOffset + (height - 1 - srcY) * rowSize
  104.       local pixelStart = rowStart + srcX * 3
  105.       local b, g, r = stringUnpack("BBB", imageData, pixelStart + 1)
  106.       table.insert(decodedImage, {r, g, b})
  107.       yieldIfNeeded()
  108.     end
  109.   end
  110.   return decodedImage, 160, 50
  111. end
  112. local function decodeTIImageMode1(imageData)
  113.   local width = stringUnpack("<I4", imageData, 5)
  114.   local height = stringUnpack("<I4", imageData, 9)
  115.   local pixelData = imageData:sub(21)
  116.   local decodedImage = {}
  117.   for i = 1, #pixelData, 2 do
  118.     local pixel = stringUnpack("<I2", pixelData, i)
  119.     local r = bit32.rshift(bit32.band(pixel, 0x7C00), 10) * 8
  120.     local g = bit32.rshift(bit32.band(pixel, 0x03E0), 5) * 8
  121.     local b = bit32.band(pixel, 0x001F) * 8
  122.     table.insert(decodedImage, {r, g, b})
  123.     yieldIfNeeded()
  124.   end
  125.   return decodedImage, width, height
  126. end
  127. local function decodeImageMode1(imageData)
  128.   if not imageData or imageData == "" then
  129.     error("No image data received")
  130.   end
  131.   local format = imageData:sub(1, 2)
  132.   if format == "BM" then
  133.     return decodeBMPMode1(imageData)
  134.   elseif format == "TI" then
  135.     return decodeTIImageMode1(imageData)
  136.   else
  137.     error("Unsupported image format")
  138.   end
  139. end
  140. local function packColor(r, g, b)
  141.   return (r * 256 + g) * 256 + b
  142. end
  143. local function displayImageMode1(decodedImage, x, y, width, height)
  144.   for i = 1, #decodedImage do
  145.     local pixel = decodedImage[i]
  146.     local px = (i - 1) % width + x
  147.     local py = math.floor((i - 1) / width) + y
  148.     local color = packColor(pixel[1], pixel[2], pixel[3])
  149.     gpu.setBackground(color)
  150.     gpu.set(px, py, " ")
  151.     yieldIfNeeded()
  152.   end
  153. end
  154. -- Mode 2 specific functions
  155. local function decodeBMPMode2(imageData)
  156.     local width = stringUnpack("<I4", imageData, 19)
  157.     local height = stringUnpack("<I4", imageData, 23)
  158.     local bitsPerPixel = stringUnpack("<I2", imageData, 29)
  159.     local compression = stringUnpack("<I4", imageData, 31)
  160.     local dataOffset = stringUnpack("<I4", imageData, 11)
  161.     if bitsPerPixel ~= 24 or compression ~= 0 then
  162.         error("Unsupported BMP format. Only 24-bit uncompressed BMPs are supported.")
  163.     end
  164.     local screenWidth, screenHeight = gpu.getResolution()
  165.     local targetWidth = screenWidth * 2
  166.     local targetHeight = screenHeight * 4
  167.     local decodedImage = {}
  168.     local rowSize = math.floor((bitsPerPixel * width + 31) / 32) * 4
  169.     local xRatio = width / targetWidth
  170.     local yRatio = height / targetHeight
  171.     for y = 0, targetHeight - 1 do
  172.         for x = 0, targetWidth - 1 do
  173.             local srcX = math.floor(x * xRatio)
  174.             local srcY = math.floor(y * yRatio)
  175.             local rowStart = dataOffset + (height - 1 - srcY) * rowSize
  176.             local pixelStart = rowStart + srcX * 3
  177.             local b, g, r = stringUnpack("BBB", imageData, pixelStart + 1)
  178.             table.insert(decodedImage, {r, g, b})
  179.             yieldIfNeeded()
  180.         end
  181.     end
  182.     return decodedImage, targetWidth, targetHeight
  183. end
  184. local function getBrailleCharacter(pixels)
  185.     local brailleBase = 0x2800
  186.     local pattern = 0
  187.     local dotWeights = {1, 2, 4, 8, 16, 32, 64, 128}
  188.     local function adjustBrightness(brightness)
  189.         return math.pow(brightness / 255, 0.5) * 255
  190.     end
  191.     local thresholds = {}
  192.     for i = 1, 8 do
  193.         local brightness = (pixels[i][1] + pixels[i][2] + pixels[i][3]) / 3
  194.         thresholds[i] = adjustBrightness(brightness)
  195.     end
  196.     local avgThreshold = 0
  197.     for i = 1, 8 do
  198.         avgThreshold = avgThreshold + thresholds[i]
  199.     end
  200.     avgThreshold = avgThreshold / 8
  201.     for i = 1, 8 do
  202.         if thresholds[i] > avgThreshold then
  203.             pattern = pattern + dotWeights[i]
  204.         end
  205.     end
  206.     return unicode.char(brailleBase + pattern)
  207. end
  208. local function rgbToColor(r, g, b)
  209.     return (r * 65536) + (g * 256) + b
  210. end
  211. local function averageColor(colors)
  212.     local r, g, b, count = 0, 0, 0, #colors
  213.     for _, color in ipairs(colors) do
  214.         r = r + color[1]
  215.         g = g + color[2]
  216.         b = b + color[3]
  217.     end
  218.     if count > 0 then
  219.         return {math.floor(r / count), math.floor(g / count), math.floor(b / count)}
  220.     else
  221.         return {0, 0, 0}
  222.     end
  223. end
  224. local function blendColors(c1, c2, factor)
  225.     return {
  226.         math.floor(c1[1] * (1 - factor) + c2[1] * factor),
  227.         math.floor(c1[2] * (1 - factor) + c2[2] * factor),
  228.         math.floor(c1[3] * (1 - factor) + c2[3] * factor)
  229.     }
  230. end
  231. local function displayImageMode2(decodedImage, x, y, width, height)
  232.     for py = 0, height / 4 - 1 do
  233.         for px = 0, width / 2 - 1 do
  234.             local pixels = {}
  235.             local colors = {}
  236.            
  237.             for dy = 0, 3 do
  238.                 for dx = 0, 1 do
  239.                     local ix = px * 2 + dx
  240.                     local iy = py * 4 + dy
  241.                     local index = iy * width + ix + 1
  242.                     local pixel = decodedImage[index]
  243.                     pixels[dy * 2 + dx + 1] = pixel
  244.                     table.insert(colors, pixel)
  245.                 end
  246.             end
  247.             local char = getBrailleCharacter(pixels)
  248.             local fgColor = averageColor(colors)
  249.            
  250.             local darkestColor = colors[1]
  251.             for i = 2, #colors do
  252.                 if colors[i][1] + colors[i][2] + colors[i][3] < darkestColor[1] + darkestColor[2] + darkestColor[3] then
  253.                     darkestColor = colors[i]
  254.                 end
  255.             end
  256.             local avgColor = averageColor(colors)
  257.             local bgColor = blendColors(darkestColor, avgColor, 0.3)
  258.            
  259.             gpu.setForeground(rgbToColor(fgColor[1], fgColor[2], fgColor[3]))
  260.             gpu.setBackground(rgbToColor(bgColor[1], bgColor[2], bgColor[3]))
  261.             gpu.set(x + px, y + py, char)
  262.         end
  263.         yieldIfNeeded()
  264.     end
  265. end
  266. -- Mode 3 specific functions
  267. local function decodeBMPMode3(imageData)
  268.     local width = stringUnpack("<I4", imageData, 19)
  269.     local height = stringUnpack("<I4", imageData, 23)
  270.     local bitsPerPixel = stringUnpack("<I2", imageData, 29)
  271.     local compression = stringUnpack("<I4", imageData, 31)
  272.     local dataOffset = stringUnpack("<I4", imageData, 11)
  273.     if bitsPerPixel ~= 24 or compression ~= 0 then
  274.         error("Unsupported BMP format. Only 24-bit uncompressed BMPs are supported.")
  275.     end
  276.     local screenWidth, screenHeight = gpu.getResolution()
  277.     local targetWidth = screenWidth * 2
  278.     local targetHeight = screenHeight * 4
  279.     local decodedImage = {}
  280.     local rowSize = math.floor((bitsPerPixel * width + 31) / 32) * 4
  281.     local xRatio = width / targetWidth
  282.     local yRatio = height / targetHeight
  283.     for y = 0, targetHeight - 1 do
  284.         for x = 0, targetWidth - 1 do
  285.             local srcX = math.floor(x * xRatio)
  286.             local srcY = math.floor(y * yRatio)
  287.             local rowStart = dataOffset + (height - 1 - srcY) * rowSize
  288.             local pixelStart = rowStart + srcX * 3
  289.             local b, g, r = stringUnpack("BBB", imageData, pixelStart + 1)
  290.             table.insert(decodedImage, {r, g, b})
  291.             yieldIfNeeded()
  292.         end
  293.     end
  294.     return decodedImage, targetWidth, targetHeight
  295. end
  296. local function getBrailleCharacter(pixels)
  297.     local brailleBase = 0x2800
  298.     local pattern = 0
  299.     local dotWeights = {1, 2, 4, 8, 16, 32, 64, 128}
  300.     local function adjustBrightness(brightness)
  301.         return math.pow(brightness / 255, 0.5) * 255
  302.     end
  303.     local thresholds = {}
  304.     for i = 1, 8 do
  305.         local brightness = (pixels[i][1] + pixels[i][2] + pixels[i][3]) / 3
  306.         thresholds[i] = adjustBrightness(brightness)
  307.     end
  308.     local avgThreshold = 0
  309.     for i = 1, 8 do
  310.         avgThreshold = avgThreshold + thresholds[i]
  311.     end
  312.     avgThreshold = avgThreshold / 8
  313.     for i = 1, 8 do
  314.         if thresholds[i] > avgThreshold then
  315.             pattern = pattern + dotWeights[i]
  316.         end
  317.     end
  318.     return unicode.char(brailleBase + pattern)
  319. end
  320. local function rgbToColor(r, g, b)
  321.     return (r * 65536) + (g * 256) + b
  322. end
  323. local function averageColor(colors)
  324.     local r, g, b, count = 0, 0, 0, #colors
  325.     for _, color in ipairs(colors) do
  326.         r = r + color[1]
  327.         g = g + color[2]
  328.         b = b + color[3]
  329.     end
  330.     if count > 0 then
  331.         return {math.floor(r / count), math.floor(g / count), math.floor(b / count)}
  332.     else
  333.         return {0, 0, 0}
  334.     end
  335. end
  336. local function displayImageMode3(decodedImage, x, y, width, height)
  337.     for py = 0, height / 4 - 1 do
  338.         for px = 0, width / 2 - 1 do
  339.             local pixels = {}
  340.             local colors = {}
  341.            
  342.             for dy = 0, 3 do
  343.                 for dx = 0, 1 do
  344.                     local ix = px * 2 + dx
  345.                     local iy = py * 4 + dy
  346.                     local index = iy * width + ix + 1
  347.                     local pixel = decodedImage[index]
  348.                     pixels[dy * 2 + dx + 1] = pixel
  349.                     table.insert(colors, pixel)
  350.                 end
  351.             end
  352.             local char = getBrailleCharacter(pixels)
  353.             local fgColor = averageColor(colors)
  354.             local bgColor = averageColor({colors[1], colors[8]})
  355.            
  356.             gpu.setForeground(rgbToColor(fgColor[1], fgColor[2], fgColor[3]))
  357.             gpu.setBackground(rgbToColor(bgColor[1], bgColor[2], bgColor[3]))
  358.             gpu.set(x + px, y + py, char)
  359.         end
  360.         yieldIfNeeded()
  361.     end
  362. end
  363. local function decodeBMPMode4(imageData)
  364.     local width = stringUnpack("<I4", imageData, 19)
  365.     local height = stringUnpack("<I4", imageData, 23)
  366.     local bitsPerPixel = stringUnpack("<I2", imageData, 29)
  367.     local compression = stringUnpack("<I4", imageData, 31)
  368.     local dataOffset = stringUnpack("<I4", imageData, 11)
  369.     if bitsPerPixel ~= 24 or compression ~= 0 then
  370.         error("Unsupported BMP format. Only 24-bit uncompressed BMPs are supported.")
  371.     end
  372.     local screenWidth, screenHeight = gpu.getResolution()
  373.     local targetWidth = screenWidth * 2
  374.     local targetHeight = screenHeight * 2
  375.     local decodedImage = {}
  376.     local rowSize = math.floor((bitsPerPixel * width + 31) / 32) * 4
  377.     local xRatio = width / targetWidth
  378.     local yRatio = height / targetHeight
  379.     for y = 0, targetHeight - 1 do
  380.         for x = 0, targetWidth - 1 do
  381.             local srcX = math.floor(x * xRatio)
  382.             local srcY = math.floor(y * yRatio)
  383.             local rowStart = dataOffset + (height - 1 - srcY) * rowSize
  384.             local pixelStart = rowStart + srcX * 3
  385.             local b, g, r = stringUnpack("BBB", imageData, pixelStart + 1)
  386.             table.insert(decodedImage, {r, g, b})
  387.             yieldIfNeeded()
  388.         end
  389.     end
  390.     return decodedImage, targetWidth, targetHeight
  391. end
  392. local function displayImageMode4(decodedImage, x, y, width, height)
  393.     for py = 0, height / 2 - 1 do
  394.         for px = 0, width / 2 - 1 do
  395.             local index1 = (py * 2) * width + (px * 2) + 1
  396.             local index2 = (py * 2) * width + (px * 2) + 2
  397.             local index3 = (py * 2 + 1) * width + (px * 2) + 1
  398.             local index4 = (py * 2 + 1) * width + (px * 2) + 2
  399.             local pixel1 = decodedImage[index1]
  400.             local pixel2 = decodedImage[index2]
  401.             local pixel3 = decodedImage[index3]
  402.             local pixel4 = decodedImage[index4]
  403.             local fgColor = averageColor({pixel1, pixel3})
  404.             local bgColor = averageColor({pixel2, pixel4})
  405.             gpu.setForeground(rgbToColor(fgColor[1], fgColor[2], fgColor[3]))
  406.             gpu.setBackground(rgbToColor(bgColor[1], bgColor[2], bgColor[3]))
  407.             gpu.set(x + px, y + py, "▀")
  408.         end
  409.         yieldIfNeeded()
  410.     end
  411. end
  412. local function decodeBMPMode5(imageData)
  413.     local width = stringUnpack("<I4", imageData, 19)
  414.     local height = stringUnpack("<I4", imageData, 23)
  415.     local bitsPerPixel = stringUnpack("<I2", imageData, 29)
  416.     local compression = stringUnpack("<I4", imageData, 31)
  417.     local dataOffset = stringUnpack("<I4", imageData, 11)
  418.     if bitsPerPixel ~= 24 or compression ~= 0 then
  419.         error("Unsupported BMP format. Only 24-bit uncompressed BMPs are supported.")
  420.     end
  421.     local screenWidth, screenHeight = gpu.getResolution()
  422.     local targetWidth = screenWidth * 2
  423.     local targetHeight = screenHeight * 2
  424.     local decodedImage = {}
  425.     local rowSize = math.floor((bitsPerPixel * width + 31) / 32) * 4
  426.     local xRatio = width / targetWidth
  427.     local yRatio = height / targetHeight
  428.     for y = 0, targetHeight - 1 do
  429.         for x = 0, targetWidth - 1 do
  430.             local srcX = math.floor(x * xRatio)
  431.             local srcY = math.floor(y * yRatio)
  432.             local rowStart = dataOffset + (height - 1 - srcY) * rowSize
  433.             local pixelStart = rowStart + srcX * 3
  434.             local b, g, r = stringUnpack("BBB", imageData, pixelStart + 1)
  435.             table.insert(decodedImage, {r, g, b})
  436.             yieldIfNeeded()
  437.         end
  438.     end
  439.     return decodedImage, targetWidth, targetHeight
  440. end
  441. local function displayImageMode5(decodedImage, x, y, width, height)
  442.     for py = 0, height / 2 - 1 do
  443.         for px = 0, width / 2 - 1 do
  444.             local index1 = (py * 2) * width + (px * 2) + 1
  445.             local index2 = (py * 2) * width + (px * 2) + 2
  446.             local index3 = (py * 2 + 1) * width + (px * 2) + 1
  447.             local index4 = (py * 2 + 1) * width + (px * 2) + 2
  448.             local pixels = {decodedImage[index1], decodedImage[index2], decodedImage[index3], decodedImage[index4]}
  449.             local avgColor = averageColor(pixels)
  450.             local fgColor = {math.floor(avgColor[1] / 16), math.floor(avgColor[2] / 16), math.floor(avgColor[3] / 16)}
  451.             local bgColor = {math.floor(pixels[4][1] / 16), math.floor(pixels[4][2] / 16), math.floor(pixels[4][3] / 16)}
  452.             gpu.setForeground(rgbToColor(fgColor[1] * 16, fgColor[2] * 16, fgColor[3] * 16))
  453.             gpu.setBackground(rgbToColor(bgColor[1] * 16, bgColor[2] * 16, bgColor[3] * 16))
  454.             gpu.set(x + px, y + py, "▄")
  455.         end
  456.         yieldIfNeeded()
  457.     end
  458. end
  459. local function decodeBMPMode6(imageData)
  460.     local width = stringUnpack("<I4", imageData, 19)
  461.     local height = stringUnpack("<I4", imageData, 23)
  462.     local bitsPerPixel = stringUnpack("<I2", imageData, 29)
  463.     local compression = stringUnpack("<I4", imageData, 31)
  464.     local dataOffset = stringUnpack("<I4", imageData, 11)
  465.     if bitsPerPixel ~= 24 or compression ~= 0 then
  466.         error("Unsupported BMP format. Only 24-bit uncompressed BMPs are supported.")
  467.     end
  468.     local targetWidth = 640
  469.     local targetHeight = 200
  470.     local decodedImage = {}
  471.     local rowSize = math.floor((bitsPerPixel * width + 31) / 32) * 4
  472.     local xRatio = width / targetWidth
  473.     local yRatio = height / targetHeight
  474.     for y = 0, targetHeight - 1 do
  475.         for x = 0, targetWidth - 1 do
  476.             local srcX = math.floor(x * xRatio)
  477.             local srcY = math.floor(y * yRatio)
  478.             local rowStart = dataOffset + (height - 1 - srcY) * rowSize
  479.             local pixelStart = rowStart + srcX * 3
  480.             local b, g, r = stringUnpack("BBB", imageData, pixelStart + 1)
  481.             table.insert(decodedImage, {r, g, b})
  482.             yieldIfNeeded()
  483.         end
  484.     end
  485.     return decodedImage, targetWidth, targetHeight
  486. end
  487. local function getBrailleCharacterMode6(pixels)
  488.     local brailleBase = 0x2800
  489.     local pattern = 0
  490.     local dotWeights = {1, 2, 4, 8, 16, 32, 64, 128}
  491.     local function adjustBrightness(brightness)
  492.         return math.pow(brightness / 255, 0.5) * 255
  493.     end
  494.     local thresholds = {}
  495.     for i = 1, 8 do
  496.         local brightness = (pixels[i][1] + pixels[i][2] + pixels[i][3]) / 3
  497.         thresholds[i] = adjustBrightness(brightness)
  498.     end
  499.     local avgThreshold = 0
  500.     for i = 1, 8 do
  501.         avgThreshold = avgThreshold + thresholds[i]
  502.     end
  503.     avgThreshold = avgThreshold / 8
  504.     for i = 1, 8 do
  505.         if thresholds[i] > avgThreshold then
  506.             pattern = pattern + dotWeights[i]
  507.         end
  508.     end
  509.     return unicode.char(brailleBase + pattern)
  510. end
  511. local function getBrailleCharacterMode6(pixels)
  512.     local brailleBase = 0x2800
  513.     local pattern = 0
  514.     local dotWeights = {1, 2, 4, 8}
  515.     local function adjustBrightness(brightness)
  516.         return math.pow(brightness / 255, 0.5) * 255
  517.     end
  518.     local thresholds = {}
  519.     for i = 1, 4 do
  520.         local brightness = (pixels[i][1] + pixels[i][2] + pixels[i][3]) / 3
  521.         thresholds[i] = adjustBrightness(brightness)
  522.     end
  523.     local avgThreshold = 0
  524.     for i = 1, 4 do
  525.         avgThreshold = avgThreshold + thresholds[i]
  526.     end
  527.     avgThreshold = avgThreshold / 4
  528.     for i = 1, 4 do
  529.         if thresholds[i] > avgThreshold then
  530.             pattern = pattern + dotWeights[i]
  531.         end
  532.     end
  533.     return unicode.char(brailleBase + pattern)
  534. end
  535. local function displayImageMode6(decodedImage, x, y, width, height)
  536.     for py = 0, 49 do
  537.         for px = 0, 159 do
  538.             local pixels = {}
  539.             for dy = 0, 3 do
  540.                 for dx = 0, 1 do
  541.                     local ix = px * 4 + dx
  542.                     local iy = py * 4 + dy
  543.                     local index = iy * width + ix + 1
  544.                     table.insert(pixels, decodedImage[index])
  545.                 end
  546.             end
  547.             local char = getBrailleCharacterMode6(pixels)
  548.             local fgColor = averageColor({pixels[1], pixels[2], pixels[3], pixels[4]})
  549.             local bgColor = averageColor({pixels[5], pixels[6], pixels[7], pixels[8]})
  550.            
  551.             gpu.setForeground(rgbToColor(fgColor[1], fgColor[2], fgColor[3]))
  552.             gpu.setBackground(rgbToColor(bgColor[1], bgColor[2], bgColor[3]))
  553.             gpu.set(x + px, y + py, char)
  554.         end
  555.         yieldIfNeeded()
  556.     end
  557. end
  558. local function decodeBMPMode7(imageData)
  559.     local width = stringUnpack("<I4", imageData, 19)
  560.     local height = stringUnpack("<I4", imageData, 23)
  561.     local bitsPerPixel = stringUnpack("<I2", imageData, 29)
  562.     local compression = stringUnpack("<I4", imageData, 31)
  563.     local dataOffset = stringUnpack("<I4", imageData, 11)
  564.     if bitsPerPixel ~= 24 or compression ~= 0 then
  565.         error("Unsupported BMP format. Only 24-bit uncompressed BMPs are supported.")
  566.     end
  567.     local targetWidth = 640
  568.     local targetHeight = 400
  569.     local decodedImage = {}
  570.     local rowSize = math.floor((bitsPerPixel * width + 31) / 32) * 4
  571.     local xRatio = width / targetWidth
  572.     local yRatio = height / targetHeight
  573.     for y = 0, targetHeight - 1 do
  574.         for x = 0, targetWidth - 1 do
  575.             local srcX = math.floor(x * xRatio)
  576.             local srcY = math.floor(y * yRatio)
  577.             local rowStart = dataOffset + (height - 1 - srcY) * rowSize
  578.             local pixelStart = rowStart + srcX * 3
  579.             local b, g, r = stringUnpack("BBB", imageData, pixelStart + 1)
  580.             table.insert(decodedImage, {r, g, b})
  581.             yieldIfNeeded()
  582.         end
  583.     end
  584.     return decodedImage, targetWidth, targetHeight
  585. end
  586. local function getBrailleCharacterMode7(pixels)
  587.     local brailleBase = 0x2800
  588.     local pattern = 0
  589.     local dotWeights = {1, 2, 4, 8, 16, 32, 64, 128}
  590.     local function adjustBrightness(brightness)
  591.         return math.pow(brightness / 255, 0.7) * 255
  592.     end
  593.     local thresholds = {}
  594.     for i = 1, 8 do
  595.         local brightness = (pixels[i][1] * 0.299 + pixels[i][2] * 0.587 + pixels[i][3] * 0.114)
  596.         thresholds[i] = adjustBrightness(brightness)
  597.     end
  598.     local avgThreshold = 0
  599.     for i = 1, 8 do
  600.         avgThreshold = avgThreshold + thresholds[i]
  601.     end
  602.     avgThreshold = avgThreshold / 8
  603.     for i = 1, 8 do
  604.         if thresholds[i] > avgThreshold then
  605.             pattern = pattern + dotWeights[i]
  606.         end
  607.     end
  608.     return unicode.char(brailleBase + pattern)
  609. end
  610. local function rgbToLabMode7(r, g, b)
  611.     r, g, b = r / 255, g / 255, b / 255
  612.     local x = 0.4124564 * r + 0.3575761 * g + 0.1804375 * b
  613.     local y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b
  614.     local z = 0.0193339 * r + 0.1191920 * g + 0.9503041 * b
  615.     local function f(t)
  616.         if t > 0.008856 then
  617.             return math.pow(t, 1/3)
  618.         else
  619.             return 7.787 * t + 16/116
  620.         end
  621.     end
  622.     x, y, z = f(x / 0.95047), f(y), f(z / 1.08883)
  623.     return 116 * y - 16, 500 * (x - y), 200 * (y - z)
  624. end
  625. local function colorDistanceMode7(c1, c2)
  626.     local l1, a1, b1 = rgbToLabMode7(c1[1], c1[2], c1[3])
  627.     local l2, a2, b2 = rgbToLabMode7(c2[1], c2[2], c2[3])
  628.     local dl, da, db = l1 - l2, a1 - a2, b1 - b2
  629.     return math.sqrt(dl * dl + da * da + db * db)
  630. end
  631. local function findClosestColorMode7(color, palette)
  632.     local minDistance = math.huge
  633.     local closestColor = palette[1]
  634.     for _, paletteColor in ipairs(palette) do
  635.         local distance = colorDistanceMode7(color, paletteColor)
  636.         if distance < minDistance then
  637.             minDistance = distance
  638.             closestColor = paletteColor
  639.         end
  640.     end
  641.     return closestColor
  642. end
  643. local function createDitherPaletteMode7()
  644.     local palette = {}
  645.     for r = 0, 5 do
  646.         for g = 0, 5 do
  647.             for b = 0, 5 do
  648.                 table.insert(palette, {r * 51, g * 51, b * 51})
  649.             end
  650.         end
  651.     end
  652.     for i = 0, 15 do
  653.         table.insert(palette, {i * 17, i * 17, i * 17})
  654.     end
  655.     return palette
  656. end
  657. local function displayImageMode7(decodedImage, x, y, width, height)
  658.     local ditherPalette = createDitherPaletteMode7()
  659.     local screenWidth, screenHeight = 160, 50  -- Fixed for tier 3 GPU
  660.    
  661.     local function getDitheredColor(ix, iy)
  662.         local index = iy * width + ix + 1
  663.         local pixel = decodedImage[index] or {0, 0, 0}
  664.         local ditherMatrix = {
  665.             {0, 8, 2, 10},
  666.             {12, 4, 14, 6},
  667.             {3, 11, 1, 9},
  668.             {15, 7, 13, 5}
  669.         }
  670.         local ditherValue = ditherMatrix[iy % 4 + 1][ix % 4 + 1] / 16
  671.         local ditheredColor = {
  672.             math.min(255, pixel[1] + (ditherValue - 0.5) * 32),
  673.             math.min(255, pixel[2] + (ditherValue - 0.5) * 32),
  674.             math.min(255, pixel[3] + (ditherValue - 0.5) * 32)
  675.         }
  676.         return findClosestColorMode7(ditheredColor, ditherPalette)
  677.     end
  678.    
  679.     for py = 0, screenHeight - 1 do
  680.         for px = 0, screenWidth - 1 do
  681.             local pixels = {}
  682.             local colors = {}
  683.            
  684.             for dy = 0, 3 do
  685.                 for dx = 0, 1 do
  686.                     local ix = px * 4 + dx
  687.                     local iy = py * 8 + dy
  688.                     local index = iy * width + ix + 1
  689.                     local pixel = decodedImage[index] or {0, 0, 0}  -- Default to black if out of bounds
  690.                     pixels[dy * 2 + dx + 1] = pixel
  691.                     table.insert(colors, pixel)
  692.                 end
  693.             end
  694.             local char = getBrailleCharacterMode7(pixels)
  695.             local avgColor = averageColor(colors)
  696.             local fgColor = findClosestColorMode7(avgColor, ditherPalette)
  697.             local ditheredColor = getDitheredColor(px * 4, py * 8)
  698.            
  699.             gpu.setForeground(rgbToColor(fgColor[1], fgColor[2], fgColor[3]))
  700.             gpu.setBackground(rgbToColor(ditheredColor[1], ditheredColor[2], ditheredColor[3]))
  701.             gpu.set(x + px, y + py, char)
  702.         end
  703.         yieldIfNeeded()
  704.     end
  705. end
  706.  
  707. -- Sharpen image using unsharp mask technique
  708. local function sharpenImage(pixels, width, height)
  709.   local sharpened = {}
  710.   local kernel = {
  711.     {0, -1, 0},
  712.     {-1, 5, -1},
  713.     {0, -1, 0}
  714.   }
  715.  
  716.   for y = 0, height - 1 do
  717.     for x = 0, width - 1 do
  718.       local r, g, b = 0, 0, 0
  719.       for ky = -1, 1 do
  720.         for kx = -1, 1 do
  721.           local nx = math.min(math.max(x + kx, 0), width - 1)
  722.           local ny = math.min(math.max(y + ky, 0), height - 1)
  723.           local idx = ny * width + nx + 1
  724.           local p = pixels[idx]
  725.           local k = kernel[ky + 2][kx + 2]
  726.           r = r + p[1] * k
  727.           g = g + p[2] * k
  728.           b = b + p[3] * k
  729.         end
  730.       end
  731.       r = math.min(math.max(math.floor(r), 0), 255)
  732.       g = math.min(math.max(math.floor(g), 0), 255)
  733.       b = math.min(math.max(math.floor(b), 0), 255)
  734.       sharpened[y * width + x + 1] = {r, g, b}
  735.     end
  736.   end
  737.   return sharpened
  738. end
  739.  
  740. -- Full 24-bit color palette with dithering, richer than Mode 7 (which had 256 colors)
  741. local function createDitherPaletteMode8()
  742.   local palette = {}
  743.   -- Expanded 6x6x6 cube plus grayscales from Mode 7 plus additional intermediate levels for smoothness
  744.   for r = 0, 7 do
  745.     for g = 0, 7 do
  746.       for b = 0, 7 do
  747.         table.insert(palette, {r * 36, g * 36, b * 36})
  748.       end
  749.     end
  750.   end
  751.   for i = 0, 31 do
  752.     local val = i * 8
  753.     table.insert(palette, {val, val, val})
  754.   end
  755.   return palette
  756. end
  757.  
  758. local function getDitheredColorMode8(ix, iy, color, palette)
  759.   local ditherMatrix = {
  760.     {0, 8, 2, 10, 1, 9, 3, 11},
  761.     {12, 4, 14, 6, 13, 5, 15, 7},
  762.     {3, 11, 1, 9, 2, 10, 0, 8},
  763.     {15, 7, 13, 5, 14, 6, 12, 4},
  764.     {1, 9, 3, 11, 0, 8, 2, 10},
  765.     {13, 5, 15, 7, 12, 4, 14, 6},
  766.     {2, 10, 0, 8, 3, 11, 1, 9},
  767.     {14, 6, 12, 4, 15, 7, 13, 5},
  768.   }
  769.   local ditherValue = ditherMatrix[iy % 8 + 1][ix % 8 + 1] / 16
  770.   local ditheredColor = {
  771.     math.min(255, color[1] + (ditherValue - 0.5) * 48),
  772.     math.min(255, color[2] + (ditherValue - 0.5) * 48),
  773.     math.min(255, color[3] + (ditherValue - 0.5) * 48)
  774.   }
  775.   -- Find closest palette color
  776.   local minDist = math.huge
  777.   local closest = palette[1]
  778.   for _, pc in ipairs(palette) do
  779.     local dr = pc[1] - ditheredColor[1]
  780.     local dg = pc[2] - ditheredColor[2]
  781.     local db = pc[3] - ditheredColor[3]
  782.     local dist = dr * dr + dg * dg + db * db
  783.     if dist < minDist then
  784.       minDist = dist
  785.       closest = pc
  786.     end
  787.   end
  788.   return closest
  789. end
  790.  
  791. local function displayImageMode8(decodedImage, x, y, width, height)
  792.   local sharpenedImage = sharpenImage(decodedImage, width, height)
  793.   local ditherPalette = createDitherPaletteMode8()
  794.  
  795.   for py = 0, height / 4 - 1 do
  796.     for px = 0, width / 2 - 1 do
  797.       local pixels = {}
  798.       local colors = {}
  799.       for dy = 0, 3 do
  800.         for dx = 0, 1 do
  801.           local ix = px * 2 + dx
  802.           local iy = py * 4 + dy
  803.           local index = iy * width + ix + 1
  804.           local pixel = sharpenedImage[index] or {0, 0, 0}
  805.           pixels[dy * 2 + dx + 1] = pixel
  806.           table.insert(colors, pixel)
  807.         end
  808.       end
  809.       local char = getBrailleCharacter(pixels)
  810.       local avgColor = averageColor(colors)
  811.       local fgColor = getDitheredColorMode8(px * 2, py * 4, avgColor, ditherPalette)
  812.       local bgColor = getDitheredColorMode8(px * 2 + 1, py * 4 + 2, avgColor, ditherPalette)
  813.  
  814.       gpu.setForeground(rgbToColor(fgColor[1], fgColor[2], fgColor[3]))
  815.       gpu.setBackground(rgbToColor(bgColor[1], bgColor[2], bgColor[3]))
  816.       gpu.set(x + px, y + py, char)
  817.     end
  818.     yieldIfNeeded()
  819.   end
  820. end
  821.  
  822. local function OC_BMP()
  823.     term.clear()
  824.     print("░█▀█░█▀▀░░░░░█▀▄░█▄█░█▀█")
  825.     print("░█░█░█░░░▄▄▄░█▀▄░█░█░█▀▀")
  826.     print("░▀▀▀░▀▀▀░░░░░▀▀░░▀░▀░▀░░")
  827.     print("\nOC-BMP : An BMP Image Viewer For OpenComputers Coded By nonogamer9! Version 1.4")
  828.     print("\nSelect mode:")
  829.     print("1. Mode 1 (Normal Tier 3 GPU resolution of 160x50 with 256 colors and a color palette of 16 colors)")
  830.     print("2. Mode 2 (Custom resolution of 320x200 with 256 colors and a color palette of 16 colors)")
  831.     print("3. Mode 3 (Custom resolution of 320x200 with full 24-bit color and a color palette of 16 colors)")
  832.     print("4. Mode 4 (Double Tier 3 GPU resolution of 320x100 with 256 colors and a color palette of 16 colors)")
  833.     print("5. Mode 5 (Pseudo-Color Mode 320x100 with 4096 colors and a color palette of 32 colors)")
  834.     print("6. Mode 6 (High-Resolution Mode of 640x200 with 256 colors and a color palette of 16 colors)")
  835.     print("7. Mode 7 (Ultra-High Resolution Mode of 640x400 with 24 bit color and a color palette of 256 colors)")
  836.     print("8. Mode 8 (Sharpened 320x200 with enhanced 24-bit dithering and braille)")
  837.    
  838.     local mode = tonumber(io.read())
  839.    
  840.     if mode ~= 1 and mode ~= 2 and mode ~= 3 and mode ~= 4 and mode ~= 5 and mode ~= 6 and mode ~= 7 and mode ~= 8 then
  841.         print("Invalid mode. Defaulting to mode 1 (160x50)")
  842.         mode = 1
  843.     end
  844.     print("\nEnter image URL:")
  845.     local url = io.read()
  846.    
  847.     local success, result = pcall(function()
  848.         print("Downloading image...")
  849.         local imageData = loadImage(url)
  850.        
  851.         print("Decoding image...")
  852.         local decodedImage, width, height
  853.        
  854.         if mode == 1 then
  855.             decodedImage, width, height = decodeBMPMode1(imageData)
  856.         elseif mode == 2 then
  857.             decodedImage, width, height = decodeBMPMode2(imageData)
  858.         elseif mode == 3 then
  859.             decodedImage, width, height = decodeBMPMode3(imageData)
  860.         elseif mode == 4 then
  861.             decodedImage, width, height = decodeBMPMode4(imageData)
  862.         elseif mode == 5 then
  863.             decodedImage, width, height = decodeBMPMode5(imageData)
  864.         elseif mode == 6 then
  865.             decodedImage, width, height = decodeBMPMode6(imageData)
  866.         elseif mode == 7 then
  867.             decodedImage, width, height = decodeBMPMode7(imageData)
  868.         elseif mode == 8 then
  869.             decodedImage, width, height = decodeBMPMode2(imageData)
  870.         end
  871.        
  872.         print("Displaying image...")
  873.         local screenWidth, screenHeight = gpu.getResolution()
  874.         local xPos, yPos
  875.         if mode == 1 then
  876.             xPos = math.floor((screenWidth - width) / 2)
  877.             yPos = math.floor((screenHeight - height) / 2)
  878.         elseif mode == 2 or mode == 3 or mode == 8 then
  879.             xPos = math.floor((screenWidth - width / 2) / 2)
  880.             yPos = math.floor((screenHeight - height / 4) / 2)
  881.         elseif mode == 4 or mode == 5 then
  882.             xPos = math.floor((screenWidth - width / 2) / 2)
  883.             yPos = math.floor((screenHeight - height / 2) / 2)
  884.         elseif mode == 6 then
  885.             xPos = math.floor((screenWidth - width / 4) / 2)
  886.             yPos = math.floor((screenHeight - height / 4) / 2)
  887.         elseif mode == 7 then
  888.             xPos = math.floor((screenWidth - 160) / 2)
  889.             yPos = math.floor((screenHeight - 50) / 2)
  890.         end
  891.         if mode == 1 then
  892.             displayImageMode1(decodedImage, xPos, yPos, width, height)
  893.         elseif mode == 2 then
  894.             displayImageMode2(decodedImage, xPos, yPos, width, height)
  895.         elseif mode == 3 then
  896.             displayImageMode3(decodedImage, xPos, yPos, width, height)
  897.         elseif mode == 4 then
  898.             displayImageMode4(decodedImage, xPos, yPos, width, height)
  899.         elseif mode == 5 then
  900.             displayImageMode5(decodedImage, xPos, yPos, width, height)
  901.         elseif mode == 6 then
  902.             displayImageMode6(decodedImage, xPos, yPos, width, height)
  903.         elseif mode == 7 then
  904.             displayImageMode7(decodedImage, xPos, yPos, width, height)
  905.         elseif mode == 8 then
  906.             displayImageMode8(decodedImage, xPos, yPos, width, height)
  907.         end
  908.        
  909.         print("Press any key to exit")
  910.         event.pull("key_down")
  911.     end)
  912.     if not success then
  913.         print("Error: " .. tostring(result))
  914.         print("Press any key to exit")
  915.         event.pull("key_down")
  916.     end
  917.    
  918.     term.clear()
  919. end
  920. OC_BMP()
Advertisement
Add Comment
Please, Sign In to add comment