Advertisement
FluttyProger

TrueImage.lua

Aug 3rd, 2018
96
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 55.73 KB | None | 0 0
  1. local image = {}
  2. local unicode = require("utf8")
  3. local constants = {
  4.     OCIFSignature = "OCIF",
  5.     OCIF2Elements = {
  6.         alphaStart = "A",
  7.         symbolStart = "S",
  8.         backgroundStart = "B",
  9.         foregroundStart = "F",
  10.     },
  11.     elementCount = 4,
  12.     byteSize = 8,
  13.     nullChar = 0,
  14.     rawImageLoadStep = 19,
  15.     compressedFileFormat = ".pic",
  16.     pngFileFormat = ".png",
  17. }
  18.  
  19. local function isNan(x)
  20.   return x~=x
  21. end
  22.  
  23. function image.HEXtoRGB(color)
  24.   return bit32.rshift(color, 16), bit32.band(bit32.rshift(color, 8), 0xFF), bit32.band(color, 0xFF)
  25. end
  26.  
  27. function image.RGBtoHEX(rr, gg, bb)
  28.   return bit32.lshift(rr, 16) + bit32.lshift(gg, 8) + bb
  29. end
  30.  
  31. --HSB model
  32. function image.RGBtoHSB(rr, gg, bb)
  33.   local max = math.max(rr, math.max(gg, bb))
  34.   local min = math.min(rr, math.min(gg, bb))
  35.   local delta = max - min
  36.  
  37.   local h = 0
  38.   if ( max == rr and gg >= bb) then h = 60*(gg-bb)/delta end
  39.   if ( max == rr and gg <= bb ) then h = 60*(gg-bb)/delta + 360 end
  40.   if ( max == gg ) then h = 60*(bb-rr)/delta + 120 end
  41.   if ( max == bb ) then h = 60*(rr-gg)/delta + 240 end
  42.  
  43.   local s = 0
  44.   if ( max ~= 0 ) then s = 1 - (min / max) end
  45.  
  46.   local b = max * 100 / 255
  47.  
  48.   if isNan(h) then h = 0 end
  49.  
  50.   return h, s * 100, b
  51. end
  52.  
  53. function image.HSBtoRGB(h, s, v)
  54.   if h > 359 then h = 0 end
  55.   local rr, gg, bb = 0, 0, 0
  56.   local const = 255
  57.  
  58.   s = s/100
  59.   v = v/100
  60.  
  61.   local i = math.floor(h/60)
  62.   local f = h/60 - i
  63.  
  64.   local p = v*(1-s)
  65.   local q = v*(1-s*f)
  66.   local t = v*(1-(1-f)*s)
  67.  
  68.   if ( i == 0 ) then rr, gg, bb = v, t, p end
  69.   if ( i == 1 ) then rr, gg, bb = q, v, p end
  70.   if ( i == 2 ) then rr, gg, bb = p, v, t end
  71.   if ( i == 3 ) then rr, gg, bb = p, q, v end
  72.   if ( i == 4 ) then rr, gg, bb = t, p, v end
  73.   if ( i == 5 ) then rr, gg, bb = v, p, q end
  74.  
  75.   return math.floor(rr * const), math.floor(gg * const), math.floor(bb * const)
  76. end
  77.  
  78. function image.HEXtoHSB(color)
  79.   local rr, gg, bb = image.HEXtoRGB(color)
  80.   local h, s, b = image.RGBtoHSB( rr, gg, bb )
  81.  
  82.   return h, s, b
  83. end
  84.  
  85. function image.HSBtoHEX(h, s, b)
  86.   local rr, gg, bb = image.HSBtoRGB(h, s, b)
  87.   local color = image.RGBtoHEX(rr, gg, bb)
  88.  
  89.   return color
  90. end
  91.  
  92. --Смешивание двух цветов на основе альфа-канала второго
  93. function image.alphaBlend(firstColor, secondColor, alphaChannel)
  94.   alphaChannel = alphaChannel / 255
  95.   local invertedAlphaChannel = 1 - alphaChannel
  96.  
  97.  
  98.   local firstColorRed, firstColorGreen, firstColorBlue = image.HEXtoRGB(firstColor)
  99.   local secondColorRed, secondColorGreen, secondColorBlue = image.HEXtoRGB(secondColor)
  100.  
  101.   return image.RGBtoHEX(
  102.     secondColorRed * invertedAlphaChannel + firstColorRed * alphaChannel,
  103.     secondColorGreen * invertedAlphaChannel + firstColorGreen * alphaChannel,
  104.     secondColorBlue * invertedAlphaChannel + firstColorBlue * alphaChannel
  105.   )
  106. end
  107.  
  108. --Получение среднего цвета между перечисленными. К примеру, между черным и белым выдаст серый.
  109. function image.getAverageColor(colors)
  110.   local sColors = #colors
  111.   local averageRed, averageGreen, averageBlue = 0, 0, 0
  112.   for i = 1, sColors do
  113.     local r, g, b = image.HEXtoRGB(colors[i])
  114.     averageRed, averageGreen, averageBlue = averageRed + r, averageGreen + g, averageBlue + b
  115.   end
  116.   return image.RGBtoHEX(math.floor(averageRed / sColors), math.floor(averageGreen / sColors), math.floor(averageBlue / sColors))
  117. end
  118.  
  119. -----------------------------------------------------------------------------------------------------------------------
  120.  
  121. local palette = {
  122.   0x000000, 0x000040, 0x000080, 0x0000BF, 0x0000FF, 0x002400, 0x002440, 0x002480, 0x0024BF, 0x0024FF, 0x004900, 0x004940, 0x004980, 0x0049BF, 0x0049FF, 0x006D00,
  123.   0x006D40, 0x006D80, 0x006DBF, 0x006DFF, 0x009200, 0x009240, 0x009280, 0x0092BF, 0x0092FF, 0x00B600, 0x00B640, 0x00B680, 0x00B6BF, 0x00B6FF, 0x00DB00, 0x00DB40,
  124.   0x00DB80, 0x00DBBF, 0x00DBFF, 0x00FF00, 0x00FF40, 0x00FF80, 0x00FFBF, 0x00FFFF, 0x0F0F0F, 0x1E1E1E, 0x2D2D2D, 0x330000, 0x330040, 0x330080, 0x3300BF, 0x3300FF,
  125.   0x332400, 0x332440, 0x332480, 0x3324BF, 0x3324FF, 0x334900, 0x334940, 0x334980, 0x3349BF, 0x3349FF, 0x336D00, 0x336D40, 0x336D80, 0x336DBF, 0x336DFF, 0x339200,
  126.   0x339240, 0x339280, 0x3392BF, 0x3392FF, 0x33B600, 0x33B640, 0x33B680, 0x33B6BF, 0x33B6FF, 0x33DB00, 0x33DB40, 0x33DB80, 0x33DBBF, 0x33DBFF, 0x33FF00, 0x33FF40,
  127.   0x33FF80, 0x33FFBF, 0x33FFFF, 0x3C3C3C, 0x4B4B4B, 0x5A5A5A, 0x660000, 0x660040, 0x660080, 0x6600BF, 0x6600FF, 0x662400, 0x662440, 0x662480, 0x6624BF, 0x6624FF,
  128.   0x664900, 0x664940, 0x664980, 0x6649BF, 0x6649FF, 0x666D00, 0x666D40, 0x666D80, 0x666DBF, 0x666DFF, 0x669200, 0x669240, 0x669280, 0x6692BF, 0x6692FF, 0x66B600,
  129.   0x66B640, 0x66B680, 0x66B6BF, 0x66B6FF, 0x66DB00, 0x66DB40, 0x66DB80, 0x66DBBF, 0x66DBFF, 0x66FF00, 0x66FF40, 0x66FF80, 0x66FFBF, 0x66FFFF, 0x696969, 0x787878,
  130.   0x878787, 0x969696, 0x990000, 0x990040, 0x990080, 0x9900BF, 0x9900FF, 0x992400, 0x992440, 0x992480, 0x9924BF, 0x9924FF, 0x994900, 0x994940, 0x994980, 0x9949BF,
  131.   0x9949FF, 0x996D00, 0x996D40, 0x996D80, 0x996DBF, 0x996DFF, 0x999200, 0x999240, 0x999280, 0x9992BF, 0x9992FF, 0x99B600, 0x99B640, 0x99B680, 0x99B6BF, 0x99B6FF,
  132.   0x99DB00, 0x99DB40, 0x99DB80, 0x99DBBF, 0x99DBFF, 0x99FF00, 0x99FF40, 0x99FF80, 0x99FFBF, 0x99FFFF, 0xA5A5A5, 0xB4B4B4, 0xC3C3C3, 0xCC0000, 0xCC0040, 0xCC0080,
  133.   0xCC00BF, 0xCC00FF, 0xCC2400, 0xCC2440, 0xCC2480, 0xCC24BF, 0xCC24FF, 0xCC4900, 0xCC4940, 0xCC4980, 0xCC49BF, 0xCC49FF, 0xCC6D00, 0xCC6D40, 0xCC6D80, 0xCC6DBF,
  134.   0xCC6DFF, 0xCC9200, 0xCC9240, 0xCC9280, 0xCC92BF, 0xCC92FF, 0xCCB600, 0xCCB640, 0xCCB680, 0xCCB6BF, 0xCCB6FF, 0xCCDB00, 0xCCDB40, 0xCCDB80, 0xCCDBBF, 0xCCDBFF,
  135.   0xCCFF00, 0xCCFF40, 0xCCFF80, 0xCCFFBF, 0xCCFFFF, 0xD2D2D2, 0xE1E1E1, 0xF0F0F0, 0xFF0000, 0xFF0040, 0xFF0080, 0xFF00BF, 0xFF00FF, 0xFF2400, 0xFF2440, 0xFF2480,
  136.   0xFF24BF, 0xFF24FF, 0xFF4900, 0xFF4940, 0xFF4980, 0xFF49BF, 0xFF49FF, 0xFF6D00, 0xFF6D40, 0xFF6D80, 0xFF6DBF, 0xFF6DFF, 0xFF9200, 0xFF9240, 0xFF9280, 0xFF92BF,
  137.   0xFF92FF, 0xFFB600, 0xFFB640, 0xFFB680, 0xFFB6BF, 0xFFB6FF, 0xFFDB00, 0xFFDB40, 0xFFDB80, 0xFFDBBF, 0xFFDBFF, 0xFFFF00, 0xFFFF40, 0xFFFF80, 0xFFFFBF, 0xFFFFFF,
  138. }
  139.  
  140. function image.convert24BitTo8Bit(hex24)
  141.   local encodedIndex = nil
  142.   local colorMatchFactor = nil
  143.   local colorMatchFactor_min = math.huge
  144.  
  145.   local red24, green24, blue24 = image.HEXtoRGB(hex24)
  146.  
  147.   for colorIndex, colorPalette in ipairs(palette) do
  148.     local redPalette, greenPalette, bluePalette = image.HEXtoRGB(colorPalette)
  149.  
  150.     colorMatchFactor = (redPalette-red24)^2 + (greenPalette-green24)^2 + (bluePalette-blue24)^2
  151.  
  152.     if (colorMatchFactor < colorMatchFactor_min) then
  153.       encodedIndex = colorIndex
  154.       colorMatchFactor_min = colorMatchFactor
  155.     end
  156.   end
  157.    
  158.   return encodedIndex - 1
  159.   -- return searchClosestColor(1, #palette, hex24)
  160. end
  161.  
  162. function image.convert8BitTo24Bit(hex8)
  163.   return palette[hex8 + 1]
  164. end
  165.  
  166. function image.debugColorCompression(color)
  167.   local compressedColor = image.convert24BitTo8Bit(color)
  168.   local decompressedColor = image.convert8BitTo24Bit(compressedColor)
  169.   print("Исходный цвет: " .. string.format("0x%06X", color))
  170.   print("Сжатый цвет: " .. string.format("0x%02X", compressedColor))
  171.   print("Расжатый цвет: " .. string.format("0x%06X", decompressedColor))
  172. end
  173.  
  174. -------------------------------------- Локальные функции -------------------------------------------------------------------
  175.  
  176. --Формула конвертации индекса массива изображения в абсолютные координаты пикселя изображения
  177. local function convertIndexToCoords(index, width)
  178.     --Приводим индекс к корректному виду (1 = 1, 4 = 2, 7 = 3, 10 = 4, 13 = 5, ...)
  179.     index = (index + constants.elementCount - 1) / constants.elementCount
  180.     --Получаем остаток от деления индекса на ширину изображения
  181.     local ostatok = index % width
  182.     --Если остаток равен 0, то х равен ширине изображения, а если нет, то х равен остатку
  183.     local x = (ostatok == 0) and width or ostatok
  184.     --А теперь как два пальца получаем координату по Y
  185.     local y = math.ceil(index / width)
  186.     --Очищаем остаток из оперативки
  187.     ostatok = nil
  188.     --Возвращаем координаты
  189.     return x, y
  190. end
  191.  
  192. --Формула конвертации абсолютных координат пикселя изображения в индекс для массива изображения
  193. local function convertCoordsToIndex(x, y, width)
  194.     return (width * (y - 1) + x) * constants.elementCount - constants.elementCount + 1
  195. end
  196.  
  197. --Костыльное получение размера массива, ибо автор луа не позволяет
  198. --подсчитывать ненумерические индексы через #massiv
  199. --мда, мда
  200. --...
  201. --мда
  202. local function getArraySize(array)
  203.     local size = 0
  204.     for key in pairs(array) do
  205.         size = size + 1
  206.     end
  207.     return size
  208. end
  209.  
  210. --Получить количество байт, которое можно извлечь из указанного числа
  211. local function getCountOfBytes(number)
  212.     if number == 0 or number == 1 then return 1 end
  213.     return math.ceil(math.log(number, 256))
  214. end
  215.  
  216. --Распидорасить число на составляющие байты
  217. local function extractBytesFromNumber(number, countOfBytesToExtract)
  218.     local bytes = {}
  219.     local byteCutter = 0xff
  220.     for i = 1, countOfBytesToExtract do
  221.         table.insert(bytes, 1, bit32.rshift(bit32.band(number, byteCutter), (i-1)*8))
  222.         byteCutter = bit32.lshift(byteCutter, 8)
  223.     end
  224.     return unpack(bytes)
  225. end
  226.  
  227. --Склеить байты и создать из них число
  228. local function mergeBytesToNumber(...)
  229.     local bytes = {...}
  230.     local finalNumber = bytes[1]
  231.     for i = 2, #bytes do
  232.         finalNumber = bit32.bor(bit32.lshift(finalNumber, 8), bytes[i])
  233.     end
  234.     return finalNumber
  235. end
  236.  
  237. -- Сконвертировать все переданные байты в строку
  238. local function convertBytesToString(...)
  239.     local bytes = {...}
  240.     for i = 1, #bytes do
  241.         bytes[i] = string.char(bytes[i])
  242.     end
  243.     return table.concat(bytes)
  244. end
  245.  
  246. --Выделить бит-терминатор в первом байте UTF-8 символа: 1100 0010 --> 0010 0000
  247. local function selectTerminateBit_l()
  248.     local prevByte = nil
  249.     local prevTerminateBit = nil
  250.  
  251.     return function( byte )
  252.         local x, terminateBit = nil
  253.         if ( prevByte == byte ) then
  254.             return prevTerminateBit
  255.         end
  256.  
  257.         x = bit32.band( bit32.bnot(byte), 0x000000FF )
  258.         x = bit32.bor( x, bit32.rshift(x, 1) )
  259.         x = bit32.bor( x, bit32.rshift(x, 2) )
  260.         x = bit32.bor( x, bit32.rshift(x, 4) )
  261.         x = bit32.bor( x, bit32.rshift(x, 8) )
  262.         x = bit32.bor( x, bit32.rshift(x, 16) )
  263.  
  264.         terminateBit = x - bit32.rshift(x, 1)
  265.  
  266.         prevByte = byte
  267.         prevTerminateBit = terminateBit
  268.  
  269.         return terminateBit
  270.     end
  271. end
  272. local selectTerminateBit = selectTerminateBit_l()
  273.  
  274. --Прочитать n байтов из файла, возвращает прочитанные байты как число, если не удалось прочитать, то возвращает 0
  275. local function readBytes(file, count)
  276.   local readedBytes = file:read(count)
  277.   return mergeBytesToNumber(string.byte(readedBytes, 1, count))
  278. end
  279.  
  280. --Подготавливает цвета и символ для записи в файл сжатого формата
  281. local function encodePixel(background, foreground, alpha, char)
  282.     --Расхерачиваем жирные цвета в компактные цвета
  283.     local ascii_background1, ascii_background2, ascii_background3 = image.HEXtoRGB(background)
  284.     local ascii_foreground1, ascii_foreground2, ascii_foreground3 = image.HEXtoRGB(foreground)
  285.     --Расхерачиваем жирный код юникод-символа в несколько миленьких ascii-кодов
  286.     local ascii_char1, ascii_char2, ascii_char3, ascii_char4, ascii_char5, ascii_char6 = string.byte( char, 1, 6 )
  287.     ascii_char1 = ascii_char1 or constants.nullChar
  288.     --Возвращаем все расхераченное
  289.     return ascii_background1, ascii_background2, ascii_background3, ascii_foreground1, ascii_foreground2, ascii_foreground3, alpha, ascii_char1, ascii_char2, ascii_char3, ascii_char4, ascii_char5, ascii_char6
  290. end
  291.  
  292. --Декодирование UTF-8 символа
  293. local function decodeChar(file)
  294.     local first_byte = readBytes(file, 1)
  295.     local charcode_array = {first_byte}
  296.     local len = 1
  297.  
  298.     local middle = selectTerminateBit(first_byte)
  299.     if ( middle == 32 ) then
  300.         len = 2
  301.     elseif ( middle == 16 ) then
  302.         len = 3
  303.     elseif ( middle == 8 ) then
  304.         len = 4
  305.     elseif ( middle == 4 ) then
  306.         len = 5
  307.     elseif ( middle == 2 ) then
  308.         len = 6
  309.     end
  310.  
  311.     for i = 1, len-1 do
  312.         table.insert( charcode_array, readBytes(file, 1) )
  313.     end
  314.  
  315.     return string.char( unpack( charcode_array ) )
  316. end
  317.  
  318. --Правильное конвертирование HEX-переменной в строковую
  319. local function HEXtoSTRING(color, bitCount, withNull)
  320.     local stro4ka = string.format("%X",color)
  321.     local sStro4ka = unicode.len(stro4ka)
  322.  
  323.     if sStro4ka < bitCount then
  324.         stro4ka = string.rep("0", bitCount - sStro4ka) .. stro4ka
  325.     end
  326.  
  327.     sStro4ka = nil
  328.  
  329.     if withNull then return "0x"..stro4ka else return stro4ka end
  330. end
  331.  
  332. --Получение формата файла
  333. local function getFileFormat(path)
  334.     local name = fs.name(path)
  335.     local starting, ending = string.find(name, "(.)%.[%d%w]*$")
  336.     if starting == nil then
  337.         return nil
  338.     else
  339.         return unicode.sub(name, starting + 1, -1)
  340.     end
  341.     name, starting, ending = nil, nil, nil
  342. end
  343.  
  344. --Прочесть сигнатуру файла и сравнить ее с константой
  345. local function readSignature(file)
  346.     local readedSignature = file:read(4)
  347.     if readedSignature ~= constants.OCIFSignature then
  348.         file:close()
  349.         error("Can't load file: wrong OCIF format signature (\""..readedSignature .. "\" ~= \"" ..constants.OCIFSignature .. "\")")
  350.     end
  351. end
  352.  
  353. --Записать сигнатуру в файл
  354. local function writeSignature(file)
  355.     file:write(constants.OCIFSignature)
  356. end
  357.  
  358. local function sleep()
  359.   return
  360. end
  361.  
  362. --Сжать все цвета в изображении в 8-битную палитру
  363. local function convertImageColorsTo8Bit(picture)
  364.     for i = 1, #picture, 4 do
  365.         picture[i] = image.convert24BitTo8Bit(picture[i])
  366.         picture[i + 1] = image.convert24BitTo8Bit(picture[i + 1])
  367.         if i % 505 == 0 then sleep() end
  368.     end
  369.     return picture
  370. end
  371.  
  372. --Расжать все цвета в изображении в 24-битную палитру
  373. local function convertImageColorsTo24Bit(picture)
  374.     for i = 1, #picture, 4 do
  375.         picture[i] = image.convert8BitTo24Bit(picture[i])
  376.         picture[i + 1] = image.convert8BitTo24Bit(picture[i + 1])
  377.         if i % 505 == 0 then sleep() end
  378.     end
  379.     return picture
  380. end
  381.  
  382. ------------------------------ Все, что касается формата OCIF1 ------------------------------------------------------------
  383.  
  384. -- Запись в файл сжатого OCIF-формата изображения
  385. local function saveOCIF1(file, picture)
  386.     local encodedPixel
  387.     file:write( string.char( picture.width  ) )
  388.     file:write( string.char( picture.height ) )
  389.    
  390.     for i = 1, picture.width * picture.height * constants.elementCount, constants.elementCount do
  391.         encodedPixel =
  392.         {
  393.             encodePixel(picture[i], picture[i + 1], picture[i + 2], picture[i + 3])
  394.         }
  395.         for j = 1, #encodedPixel do
  396.             file:write( string.char( encodedPixel[j] ) )
  397.         end
  398.     end
  399.  
  400.     file:close()
  401. end
  402.  
  403. --Чтение из файла сжатого OCIF-формата изображения, возвращает массив типа 2 (подробнее о типах см. конец файла)
  404. local function loadOCIF1(file)
  405.     local picture = {}
  406.  
  407.     --Читаем ширину и высоту файла
  408.     picture.width = readBytes(file, 1)
  409.     picture.height = readBytes(file, 1)
  410.  
  411.     for i = 1, picture.width * picture.height do
  412.         --Читаем бекграунд
  413.         table.insert(picture, readBytes(file, 3))
  414.         --Читаем форграунд
  415.         table.insert(picture, readBytes(file, 3))
  416.         --Читаем альфу
  417.         table.insert(picture, readBytes(file, 1))
  418.         --Читаем символ
  419.         table.insert(picture, decodeChar( file ))
  420.     end
  421.  
  422.     file:close()
  423.  
  424.     return picture
  425. end
  426.  
  427. ------------------------------------------ Все, что касается формата OCIF2 ------------------------------------------------
  428.  
  429. local function saveOCIF2(file, picture, compressColors)
  430.     --Записываем ширину изображения
  431.     file:write(string.char(picture.width))
  432.     file:write(string.char(picture.height))
  433.  
  434.     --Группируем картинку
  435.     local grouppedPucture = image.convertToGroupedImage(picture)
  436.  
  437.     --Перебираем все альфы
  438.     for alpha in pairs(grouppedPucture) do
  439.         --Получаем размер массива, содержащего символы
  440.         local arraySize = getArraySize(grouppedPucture[alpha])
  441.         local countOfBytesForArraySize = getCountOfBytes(arraySize)
  442.         --Записываем в файл символ АльфаСтарта, размер массива альфы и само значение альфы
  443.         file:write(
  444.             constants.OCIF2Elements.alphaStart,
  445.             string.char(countOfBytesForArraySize),
  446.             convertBytesToString(extractBytesFromNumber(arraySize, countOfBytesForArraySize)),
  447.             string.char(alpha)
  448.         )
  449.        
  450.         for symbol in pairs(grouppedPucture[alpha]) do
  451.             --Записываем заголовок
  452.             file:write(constants.OCIF2Elements.symbolStart)
  453.             --Записываем количество всех цветов текста и символ
  454.             if compressColors then
  455.                 file:write(
  456.                     string.char(getArraySize(grouppedPucture[alpha][symbol])),
  457.                     convertBytesToString(string.byte(symbol, 1, 6))
  458.                 )
  459.             else
  460.                 file:write(
  461.                     convertBytesToString(extractBytesFromNumber(getArraySize(grouppedPucture[alpha][symbol]), 3)),
  462.                     convertBytesToString(string.byte(symbol, 1, 6))
  463.                 )
  464.             end
  465.        
  466.             for foreground in pairs(grouppedPucture[alpha][symbol]) do
  467.                 --Записываем заголовок
  468.                 file:write(constants.OCIF2Elements.foregroundStart)
  469.                 --Записываем количество цветов фона и цвет текста
  470.                 if compressColors then
  471.                     file:write(
  472.                         string.char(getArraySize(grouppedPucture[alpha][symbol][foreground])),
  473.                         string.char(foreground)
  474.                     )
  475.                 else
  476.                     file:write(
  477.                         convertBytesToString(extractBytesFromNumber(getArraySize(grouppedPucture[alpha][symbol][foreground]), 3)),
  478.                         convertBytesToString(extractBytesFromNumber(foreground, 3))
  479.                     )
  480.                 end
  481.        
  482.                 for background in pairs(grouppedPucture[alpha][symbol][foreground]) do
  483.                     --Записываем заголовок и размер массива координат
  484.                     file:write(
  485.                             constants.OCIF2Elements.backgroundStart,
  486.                             convertBytesToString(extractBytesFromNumber(getArraySize(grouppedPucture[alpha][symbol][foreground][background]), 2))
  487.                     )
  488.                     --Записываем цвет фона
  489.                     if compressColors then
  490.                         file:write(string.char(background))
  491.                     else
  492.                         file:write(convertBytesToString(extractBytesFromNumber(background, 3)))
  493.                     end
  494.            
  495.                     --Перебираем координаты
  496.                     for y in pairs(grouppedPucture[alpha][symbol][foreground][background]) do
  497.                         --Записываем заголовок координат, размер массива y и само значение y
  498.                         file:write(
  499.                             "Y",
  500.                             string.char(getArraySize(grouppedPucture[alpha][symbol][foreground][background][y])),
  501.                             string.char(y)
  502.                         )
  503.                         --Записываем ИКСЫЫЫ
  504.                         --Ы
  505.                         for i = 1, #grouppedPucture[alpha][symbol][foreground][background][y] do
  506.                             file:write(string.char(grouppedPucture[alpha][symbol][foreground][background][y][i]))
  507.                         end
  508.                     end
  509.                 end
  510.             end
  511.         end
  512.     end
  513.  
  514.     file:close()
  515. end
  516.  
  517. local function loadOCIF2(file, decompressColors, useOCIF4)
  518.     local picture = {}
  519.  
  520.     --Читаем размер изображения
  521.     local readedWidth = string.byte(file:read(1))
  522.     local readedHeight = string.byte(file:read(1))
  523.     picture.width = readedWidth
  524.     picture.height = readedHeight
  525.  
  526.     local header, alpha, symbol, foreground, background, y, alphaSize, symbolSize, foregroundSize, backgroundSize, ySize = ""
  527.     while true do
  528.         header = file:read(1)
  529.         if not header then break end
  530.         -- print("----------------------")
  531.         -- print("Заголовок: " .. header)
  532.  
  533.         if header == "A" then
  534.             local countOfBytesForArraySize = string.byte(file:read(1))
  535.             alphaSize = string.byte(file:read(countOfBytesForArraySize))
  536.             alpha = string.byte(file:read(1))
  537.             -- print("Количество байт под размер массива символов: " .. countOfBytesForArraySize)
  538.             -- print("Размер массива символов: " .. alphaSize)
  539.             -- print("Альфа: " .. alpha)
  540.  
  541.         elseif header == "S" then
  542.             if decompressColors then
  543.                 symbolSize = string.byte(file:read(1))
  544.             else
  545.                 symbolSize = mergeBytesToNumber(string.byte(file:read(3), 1, 3))
  546.             end
  547.             symbol = decodeChar(file)
  548.             -- print("Размер массива цвета текста: " .. symbolSize)
  549.             -- print("Символ: \"" .. symbol .. "\"")
  550.  
  551.         elseif header == "F" then
  552.             if decompressColors then
  553.                 foregroundSize = string.byte(file:read(1))
  554.                 foreground = image.convert8BitTo24Bit(string.byte(file:read(1)))
  555.             else
  556.                 foregroundSize = mergeBytesToNumber(string.byte(file:read(3), 1, 3))
  557.                 foreground = mergeBytesToNumber(string.byte(file:read(3), 1, 3))
  558.             end
  559.             -- print("Размер массива цвета фона: " .. foregroundSize)
  560.             -- print("Цвет текста: " .. foreground)
  561.  
  562.         elseif header == "B" then
  563.             backgroundSize = mergeBytesToNumber(string.byte(file:read(2), 1, 2))
  564.             if decompressColors then
  565.                 background = image.convert8BitTo24Bit(string.byte(file:read(1)))
  566.             else
  567.                 background = mergeBytesToNumber(string.byte(file:read(3), 1, 3))
  568.             end
  569.             -- print("Размер массива координат: " .. backgroundSize)
  570.             -- print("Цвет фона: " .. background)
  571.  
  572.             --Поддержка загрузки формата OCIF3
  573.             if not useOCIF4 then
  574.                 --Читаем координаты
  575.                 for i = 1, backgroundSize, 2 do
  576.                     local x = string.byte(file:read(1))
  577.                     local y = string.byte(file:read(1))
  578.                     local index = convertCoordsToIndex(x, y, readedWidth)
  579.                     -- print("Координата: " .. x .. "x" .. y .. ", индекс: "..index)
  580.  
  581.                     picture[index] = background
  582.                     picture[index + 1] = foreground
  583.                     picture[index + 2] = alpha
  584.                     picture[index + 3] = symbol
  585.                 end
  586.             end
  587.  
  588.         --Новый формат OCIF4
  589.         elseif header == "Y" and useOCIF4 then
  590.             ySize = string.byte(file:read(1))
  591.             y = string.byte(file:read(1))
  592.             -- print("Размер массива Y: " .. ySize)
  593.             -- print("Текущий Y: " .. y)
  594.  
  595.             for i = 1, ySize do
  596.                 local x = string.byte(file:read(1))
  597.                 local index = convertCoordsToIndex(x, y, readedWidth)
  598.                 -- print("Координата: " .. x .. "x" .. y .. ", индекс: "..index)
  599.  
  600.                 picture[index] = background
  601.                 picture[index + 1] = foreground
  602.                 picture[index + 2] = alpha
  603.                 picture[index + 3] = symbol
  604.             end    
  605.         else
  606.             error("Error while reading OCIF format: unknown Header type (" .. header .. ")")
  607.         end
  608.  
  609.     end
  610.  
  611.     file:close()
  612.  
  613.     return picture
  614. end
  615.  
  616. ------------------------------ Все, что касается формата RAW ------------------------------------------------------------
  617.  
  618. --Сохранение в файл сырого формата изображения типа 2 (подробнее о типах см. конец файла)
  619. local function saveRaw(file, picture)
  620.  
  621.     file:write("\n")
  622.  
  623.     local xPos, yPos = 1, 1
  624.     for i = 1, picture.width * picture.height * constants.elementCount, constants.elementCount do
  625.         file:write( HEXtoSTRING(picture[i], 6), " ", HEXtoSTRING(picture[i + 1], 6), " ", HEXtoSTRING(picture[i + 2], 2), " ", picture[i + 3], " ")
  626.  
  627.         xPos = xPos + 1
  628.         if xPos > picture.width then
  629.             xPos = 1
  630.             yPos = yPos + 1
  631.             file:write("\n")
  632.         end
  633.     end
  634.  
  635.     file:close()
  636. end
  637.  
  638. --Загрузка из файла сырого формата изображения типа 2 (подробнее о типах см. конец файла)
  639. local function loadRaw(file)
  640.     --Читаем один байт "прост так"
  641.     file:read(1)
  642.  
  643.     local picture = {}
  644.     local background, foreground, alpha, symbol, sLine
  645.     local lineCounter = 0
  646.  
  647.     for line in file:lines() do
  648.         sLine = unicode.len(line)
  649.         for i = 1, sLine, constants.rawImageLoadStep do
  650.             background = "0x" .. unicode.sub(line, i, i + 5)
  651.             foreground = "0x" .. unicode.sub(line, i + 7, i + 12)
  652.             alpha = "0x" .. unicode.sub(line, i + 14, i + 15)
  653.             symbol = unicode.sub(line, i + 17, i + 17)
  654.  
  655.             table.insert(picture, tonumber(background))
  656.             table.insert(picture, tonumber(foreground))
  657.             table.insert(picture, tonumber(alpha))
  658.             table.insert(picture, symbol)
  659.         end
  660.         lineCounter = lineCounter + 1
  661.     end
  662.  
  663.     picture.width = sLine / constants.rawImageLoadStep
  664.     picture.height = lineCounter
  665.  
  666.     file:close()
  667.     return picture
  668. end
  669.  
  670. ----------------------------------- Все, что касается реального PNG-формата ------------------------------------------------------------
  671.  
  672. function image.loadPng(path)
  673.     if not _G.libPNGImage then _G.libPNGImage = require("libPNGImage") end
  674.  
  675.     local success, pngImageOrErrorMessage = pcall(libPNGImage.newFromFile, path)
  676.  
  677.     if not success then
  678.         io.stderr:write(" * PNGView: PNG Loading Error *\n")
  679.         io.stderr:write("While attempting to load '" .. path .. "' as PNG, libPNGImage erred:\n")
  680.         io.stderr:write(pngImageOrErrorMessage)
  681.         return
  682.     end
  683.  
  684.     local picture = {}
  685.     picture.width, picture.height = pngImageOrErrorMessage:getSize()
  686.  
  687.     local r, g, b, a, hex
  688.     for j = 0, picture.height - 1 do
  689.         for i = 0, picture.width - 1 do
  690.             r, g, b, a = pngImageOrErrorMessage:getPixel(i, j)
  691.  
  692.             if r and g and b and a and a > 0 then
  693.                 hex = image.RGBtoHEX(r, g, b)
  694.                 table.insert(picture, hex)
  695.                 table.insert(picture, 0x000000)
  696.                 table.insert(picture, 0x00)
  697.                 table.insert(picture, " ")
  698.             end
  699.  
  700.         end
  701.     end
  702.  
  703.     return picture
  704. end
  705.  
  706. ----------------------------------- Вспомогательные функции программы ------------------------------------------------------------
  707.  
  708. --Оптимизировать и сгруппировать по цветам картинку типа 2 (подробнее о типах см. конец файла)
  709. function image.convertToGroupedImage(picture)
  710.     --Создаем массив оптимизированной картинки
  711.     local optimizedPicture = {}
  712.     --Задаем константы
  713.     local xPos, yPos, background, foreground, alpha, symbol = 1, 1, nil, nil, nil, nil
  714.     --Перебираем все элементы массива
  715.     for i = 1, picture.width * picture.height * constants.elementCount, constants.elementCount do
  716.         --Получаем символ из неоптимизированного массива
  717.         background, foreground, alpha, symbol = picture[i], picture[i + 1], picture[i + 2], picture[i + 3]
  718.         --Группируем картинку по цветам
  719.         optimizedPicture[alpha] = optimizedPicture[alpha] or {}
  720.         optimizedPicture[alpha][symbol] = optimizedPicture[alpha][symbol] or {}
  721.         optimizedPicture[alpha][symbol][foreground] = optimizedPicture[alpha][symbol][foreground] or {}
  722.         optimizedPicture[alpha][symbol][foreground][background] = optimizedPicture[alpha][symbol][foreground][background] or {}
  723.         optimizedPicture[alpha][symbol][foreground][background][yPos] = optimizedPicture[alpha][symbol][foreground][background][yPos] or {}
  724.  
  725.         table.insert(optimizedPicture[alpha][symbol][foreground][background][yPos], xPos)
  726.         --Если xPos достигает width изображения, то сбросить на 1, иначе xPos++
  727.         xPos = (xPos == picture.width) and 1 or xPos + 1
  728.         --Если xPos равняется 1, то yPos++, а если нет, то похуй
  729.         yPos = (xPos == 1) and yPos + 1 or yPos
  730.     end
  731.     --Возвращаем оптимизированный массив
  732.     return optimizedPicture
  733. end
  734.  
  735. --Нарисовать по указанным координатам картинку указанной ширины и высоты для теста
  736. function image.create(width, height, background, foreground, alpha, symbol, random)
  737.     background, foreground, alpha, symbol = background or 0x0, foreground or 0x0, alpha or 0x0, symbol or " "
  738.     local picture, symbolArray = {width = width, height = height}, {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "А", "Б", "В", "Г", "Д", "Е", "Ж", "З", "И", "Й", "К", "Л", "М", "Н", "О", "П", "Р", "С", "Т", "У", "Ф", "Х", "Ц", "Ч", "Ш", "Щ", "Ъ", "Ы", "Ь", "Э", "Ю", "Я"}
  739.     for i = 1, picture.width * picture.height do
  740.         if random then
  741.             background = math.random(0x000000, 0xffffff)
  742.             foreground = math.random(0x000000, 0xffffff)
  743.             symbol = symbolArray[math.random(1, #symbolArray)]
  744.         end
  745.         table.insert(picture, background)
  746.         table.insert(picture, foreground)
  747.         table.insert(picture, alpha)
  748.         table.insert(picture, symbol)
  749.     end
  750.     return picture
  751. end
  752.  
  753. -- Функция оптимизации цвета текста и символов у картинки, уменьшает число GPU-операций при отрисовке из буфера
  754. -- Вызывается только при сохранении файла, так что на быстродействии не сказывается,
  755. -- а в целом штука очень и очень полезная. Фиксит криворукость художников.
  756. function image.optimize(picture)
  757.     local i1, i2, i3 = 0, 0, 0
  758.     for i = 1, #picture, constants.elementCount do
  759.         --Уменьшаем нагрузку на ЦОПЕ
  760.         i1, i2, i3 = i + 1, i + 2, i + 3
  761.         --Если цвет фона равен цвету текста, и используется псевдографические полупиксели
  762.         if picture[i] == picture[i1] and (picture[i3] == "▄" or picture[i3] == "▀") then
  763.             picture[i3] = " "
  764.         end
  765.         --Если символ равен пролбелу, т.е. цвет текста не учитывается
  766.         if picture[i3] == " " then     
  767.             picture[i1] = 0x000000
  768.         end
  769.     end
  770.  
  771.     return picture
  772. end
  773.  
  774. --Получить пиксель из изображения по указанным координатам
  775. function image.get(picture, x, y)
  776.     if x >= 1 and y >= 1 and x <= picture.width and y <= picture.height then
  777.         local index = convertCoordsToIndex(x, y, picture.width)
  778.         return picture[index], picture[index + 1], picture[index + 2], picture[index + 3]
  779.     else
  780.         return nil
  781.     end
  782. end
  783.  
  784. --Установить пиксель в изображении по указанным координатам
  785. function image.set(picture, x, y, background, foreground, alpha, symbol, debug)
  786.     if x >= 1 and y >= 1 and x <= picture.width and y <= picture.height then
  787.         local index = convertCoordsToIndex(x, y, picture.width)
  788.         picture[index] = background or 0xFF00FF
  789.         picture[index + 1] = foreground or 0xFF00FF
  790.         picture[index + 2] = alpha or 0x00
  791.         picture[index + 3] = symbol or " "
  792.         return picture
  793.     else
  794.         error("Can't set pixel because it's located out of image coordinates: x = " .. x .. ", y = " .. y)
  795.     end
  796. end
  797.  
  798. ------------------------------------------ Функция снятия скриншота с экрана ------------------------------------------------
  799.  
  800. --Сделать скриншот экрана и сохранить его по указанному пути
  801. function image.screenshot(path)
  802.     local picture = {}
  803.     local foreground, background, symbol
  804.     picture.width, picture.height = component.gpu.getResolution()
  805.    
  806.     for j = 1, picture.height do
  807.         for i = 1, picture.width do
  808.             symbol, foreground, background = component.gpu.get(i, j)
  809.             table.insert(picture, background)
  810.             table.insert(picture, foreground)
  811.             table.insert(picture, 0x00)
  812.             table.insert(picture, symbol)
  813.         end
  814.     end
  815.  
  816.     image.save(path, picture)
  817. end
  818.  
  819. ------------------------------------------ Методы трансформирования изображения ------------------------------------------------
  820.  
  821. --Вставка ряда пикселей
  822. function image.insertRow(picture, y, rowArray)
  823.     local index = convertCoordsToIndex(1, y, picture.width)
  824.     for i = 1, #rowArray, 4 do
  825.         table.insert(picture, index, rowArray[i + 3])
  826.         table.insert(picture, index, rowArray[i + 2])
  827.         table.insert(picture, index, rowArray[i + 1])
  828.         table.insert(picture, index, rowArray[i])
  829.         index = index + 4
  830.     end
  831.     picture.height = picture.height + 1
  832.     return picture
  833. end
  834.  
  835. function image.insertColumn(picture, x, columnArray)
  836.     local index = convertCoordsToIndex(x, 1, picture.width)
  837.     for i = 1, #columnArray, 4 do
  838.         table.insert(picture, index, columnArray[i + 3])
  839.         table.insert(picture, index, columnArray[i + 2])
  840.         table.insert(picture, index, columnArray[i + 1])
  841.         table.insert(picture, index, columnArray[i])
  842.         index = index + picture.width * 4 + 4
  843.     end
  844.     picture.width = picture.width + 1
  845.     return picture
  846. end
  847.  
  848. --Удаление ряда пикселей
  849. function image.removeRow(picture, y)
  850.     local index = convertCoordsToIndex(1, y, picture.width)
  851.     for i = 1, picture.width * 4 do table.remove(picture, index) end
  852.     picture.height = picture.height - 1
  853.     return picture
  854. end
  855.  
  856. --Удаление колонки пикселей
  857. function image.removeColumn(picture, x)
  858.     local index = convertCoordsToIndex(x, 1, picture.width)
  859.     for i = 1, picture.height do
  860.         for j = 1, 4 do table.remove(picture, index) end
  861.         index = index + (picture.width) * 4 - 4
  862.     end
  863.     picture.width = picture.width - 1
  864.     return picture
  865. end
  866.  
  867. --Получение ряда пикселей
  868. function image.getRow(picture, y)
  869.     local row, background, foreground, alpha, symbol = {width = picture.width, height = 1}
  870.     for x = 1, picture.width do
  871.         background, foreground, alpha, symbol = image.get(picture, x, y)
  872.         table.insert(row, background)
  873.         table.insert(row, foreground)
  874.         table.insert(row, alpha)
  875.         table.insert(row, symbol)
  876.     end
  877.     return row
  878. end
  879.  
  880. --Получение колонки пикселей
  881. function image.getColumn(picture, x)
  882.     local column, background, foreground, alpha, symbol = {width = 1, height = picture.height}
  883.     for y = 1, picture.height do
  884.         background, foreground, alpha, symbol = image.get(picture, x, y)
  885.         table.insert(column, background)
  886.         table.insert(column, foreground)
  887.         table.insert(column, alpha)
  888.         table.insert(column, symbol)
  889.     end
  890.     return column
  891. end
  892.  
  893. --Создание копии массива изображения
  894. function image.duplicate(picture)
  895.     local newPicture = {width = picture.width, height = picture.height}
  896.     for i = 1, #picture do newPicture[i] = picture[i] end
  897.     return newPicture
  898. end
  899.  
  900. function her()
  901.     return 1
  902. end
  903.  
  904. --Аналог свободного трансформирования из фотошопа
  905. function image.transform(picture, newWidth, newHeight)
  906.     local newPicture = image.duplicate(picture)
  907.     local widthScale, heightScale = newWidth / picture.width, newHeight / picture.height
  908.     local deltaWidth, deltaHeight = math.abs(newWidth - picture.width), math.abs(newHeight - picture.height)
  909.     local widthIteration, heightIteration = widthScale > 1 and newWidth / deltaWidth or picture.width / deltaWidth, heightScale > 1 and newHeight / deltaHeight or picture.height / deltaHeight
  910.  
  911.     -- ecs.error(widthIteration, heightIteration, deltaWidth, picture.width, newWidth)
  912.  
  913.     --Сжимаем шакалов по ширине
  914.     if widthScale > 1 then
  915.         local x = 1
  916.         while x <= newPicture.width do
  917.             if math.floor(x % widthIteration) == 0 then newPicture = image.insertColumn(newPicture, x, image.getColumn(newPicture, x - 1)) end
  918.             x = x + 1
  919.         end
  920.     elseif widthScale < 1 then
  921.         local x = 1
  922.         while x <= newPicture.width do
  923.             if math.floor(x % widthIteration) == 0 then newPicture = image.removeColumn(newPicture, x) end
  924.             x = x + 1
  925.         end
  926.     end
  927.  
  928.     if heightScale > 1 then
  929.         local y = 1
  930.         while y <= newPicture.height do
  931.             if math.floor(y % heightIteration) == 0 then newPicture = image.insertRow(newPicture, y, image.getRow(newPicture, y - 1)) end
  932.             y = y + 1
  933.         end
  934.     elseif heightScale < 1 then
  935.         local y = 1
  936.         while y <= newPicture.height do
  937.             if math.floor(y % heightIteration) == 0 then newPicture = image.removeRow(newPicture, y) end
  938.             y = y + 1
  939.         end
  940.     end
  941.  
  942.     return newPicture
  943. end
  944.  
  945. function image.expand(picture, mode, countOfPixels, background, foreground, alpha, symbol)
  946.     local column = {}; for i = 1, picture.height do table.insert(column, background or 0xFFFFFF); table.insert(column, foreground or 0xFFFFFF); table.insert(column, alpha or 0x00); table.insert(column, symbol or " ") end
  947.     local row = {}; for i = 1, picture.height do table.insert(row, background or 0xFFFFFF); table.insert(row, foreground or 0xFFFFFF); table.insert(row, alpha or 0x00); table.insert(row, symbol or " ") end
  948.  
  949.     if mode == "fromRight" then
  950.         for i = 1, countOfPixels do picture = image.insertColumn(picture, picture.width + 1, column) end
  951.     elseif mode == "fromLeft" then
  952.         for i = 1, countOfPixels do picture = image.insertColumn(picture, 1, column) end
  953.     elseif mode == "fromTop" then
  954.         for i = 1, countOfPixels do picture = image.insertRow(picture, 1, row) end
  955.     elseif mode == "fromBottom" then
  956.         for i = 1, countOfPixels do picture = image.insertRow(picture, picture.height + 1, row) end
  957.     else
  958.         error("Wrong image expanding mode: only 'fromRight', 'fromLeft', 'fromTop' and 'fromBottom' are supported.")
  959.     end
  960.  
  961.     return picture
  962. end
  963.  
  964. function image.crop(picture, mode, countOfPixels)
  965.     if mode == "fromRight" then
  966.         for i = 1, countOfPixels do picture = image.removeColumn(picture, picture.width) end
  967.     elseif mode == "fromLeft" then
  968.         for i = 1, countOfPixels do picture = image.removeColumn(picture, 1) end
  969.     elseif mode == "fromTop" then
  970.         for i = 1, countOfPixels do picture = image.removeRow(picture, 1) end
  971.     elseif mode == "fromBottom" then
  972.         for i = 1, countOfPixels do picture = image.removeRow(picture, picture.height) end
  973.     else
  974.         error("Wrong image cropping mode: only 'fromRight', 'fromLeft', 'fromTop' and 'fromBottom' are supported.")
  975.     end
  976.  
  977.     return picture
  978. end
  979.  
  980. function image.flipVertical(picture)
  981.     local newPicture = {}; newPicture.width = picture.width; newPicture.height = picture.height
  982.     for j = picture.height, 1, -1 do
  983.         for i = 1, picture.width do
  984.             local index = convertCoordsToIndex(i, j, picture.width)
  985.             table.insert(newPicture, picture[index]); table.insert(newPicture, picture[index + 1]); table.insert(newPicture, picture[index + 2]); table.insert(newPicture, picture[index + 3])
  986.             picture[index], picture[index + 1], picture[index + 2], picture[index + 3] = nil, nil, nil, nil
  987.         end
  988.     end
  989.     return newPicture
  990. end
  991.  
  992. function image.flipHorizontal(picture)
  993.     local newPicture = {}; newPicture.width = picture.width; newPicture.height = picture.height
  994.     for j = 1, picture.height do
  995.         for i = picture.width, 1, -1 do
  996.             local index = convertCoordsToIndex(i, j, picture.width)
  997.             table.insert(newPicture, picture[index]); table.insert(newPicture, picture[index + 1]); table.insert(newPicture, picture[index + 2]); table.insert(newPicture, picture[index + 3])
  998.             picture[index], picture[index + 1], picture[index + 2], picture[index + 3] = nil, nil, nil, nil
  999.         end
  1000.     end
  1001.     return newPicture
  1002. end
  1003.  
  1004. function image.rotate(picture, angle)
  1005.     local function rotateBy90(picture)
  1006.         local newPicture = {}; newPicture.width = picture.height; newPicture.height = picture.width
  1007.         for i = 1, picture.width do
  1008.             for j = picture.height, 1, -1 do
  1009.                 local index = convertCoordsToIndex(i, j, picture.width)
  1010.                 table.insert(newPicture, picture[index]); table.insert(newPicture, picture[index + 1]); table.insert(newPicture, picture[index + 2]); table.insert(newPicture, picture[index + 3])
  1011.                 picture[index], picture[index + 1], picture[index + 2], picture[index + 3] = nil, nil, nil, nil
  1012.             end
  1013.         end
  1014.         return newPicture
  1015.     end
  1016.  
  1017.     local function rotateBy180(picture)
  1018.         local newPicture = {}; newPicture.width = picture.width; newPicture.height = picture.height
  1019.         for j = picture.height, 1, -1 do
  1020.                 for i = picture.width, 1, -1 do
  1021.                 local index = convertCoordsToIndex(i, j, picture.width)
  1022.                 table.insert(newPicture, picture[index]); table.insert(newPicture, picture[index + 1]); table.insert(newPicture, picture[index + 2]); table.insert(newPicture, picture[index + 3])
  1023.                 picture[index], picture[index + 1], picture[index + 2], picture[index + 3] = nil, nil, nil, nil
  1024.             end
  1025.         end
  1026.         return newPicture
  1027.     end
  1028.  
  1029.     local function rotateBy270(picture)
  1030.         local newPicture = {}; newPicture.width = picture.height; newPicture.height = picture.width
  1031.         for i = picture.width, 1, -1 do
  1032.             for j = 1, picture.height do
  1033.                 local index = convertCoordsToIndex(i, j, picture.width)
  1034.                 table.insert(newPicture, picture[index]); table.insert(newPicture, picture[index + 1]); table.insert(newPicture, picture[index + 2]); table.insert(newPicture, picture[index + 3])
  1035.                 picture[index], picture[index + 1], picture[index + 2], picture[index + 3] = nil, nil, nil, nil
  1036.             end
  1037.         end
  1038.         return newPicture
  1039.     end
  1040.  
  1041.     if angle == 90 then
  1042.         return rotateBy90(picture)
  1043.     elseif angle == 180 then
  1044.         return rotateBy180(picture)
  1045.     elseif angle == 270 then
  1046.         return rotateBy270(picture)
  1047.     else
  1048.         error("Can't rotate image: angle must be 90, 180 or 270 degrees.")
  1049.     end
  1050. end
  1051.  
  1052. ------------------------------------------ Функции для работы с цветом -----------------------------------------------
  1053.  
  1054. function image.hueSaturationBrightness(picture, hue, saturation, brightness)
  1055.     local function calculateBrightnessChanges(color)
  1056.         local h, s, b = image.HEXtoHSB(color)
  1057.         b = b + brightness; if b < 0 then b = 0 elseif b > 100 then b = 100 end
  1058.         s = s + saturation; if s < 0 then s = 0 elseif s > 100 then s = 100 end
  1059.         h = h + hue; if h < 0 then h = 0 elseif h > 360 then h = 360 end
  1060.         return image.HSBtoHEX(h, s, b)
  1061.     end
  1062.  
  1063.     for i = 1, #picture, 4 do
  1064.         picture[i] = calculateBrightnessChanges(picture[i])
  1065.         picture[i + 1] = calculateBrightnessChanges(picture[i + 1])
  1066.     end
  1067.  
  1068.     return picture
  1069. end
  1070.  
  1071. function image.hue(picture, hue)
  1072.     return image.hueSaturationBrightness(picture, hue, 0, 0)
  1073. end
  1074.  
  1075. function image.saturation(picture, saturation)
  1076.     return image.hueSaturationBrightness(picture, 0, saturation, 0)
  1077. end
  1078.  
  1079. function image.brightness(picture, brightness)
  1080.     return image.hueSaturationBrightness(picture, 0, 0, brightness)
  1081. end
  1082.  
  1083. function image.blackAndWhite(picture)
  1084.     return image.hueSaturationBrightness(picture, 0, -100, 0)
  1085. end
  1086.  
  1087. function image.colorBalance(picture, r, g, b)
  1088.     local function calculateRGBChanges(color)
  1089.         local rr, gg, bb = image.HEXtoRGB(color)
  1090.         rr = rr + r; gg = gg + g; bb = bb + b
  1091.         if rr < 0 then rr = 0 elseif rr > 255 then rr = 255 end
  1092.         if gg < 0 then gg = 0 elseif gg > 255 then gg = 255 end
  1093.         if bb < 0 then bb = 0 elseif bb > 255 then bb = 255 end
  1094.         return image.RGBtoHEX(rr, gg, bb)
  1095.     end
  1096.  
  1097.     for i = 1, #picture, 4 do
  1098.         picture[i] = calculateRGBChanges(picture[i])
  1099.         picture[i + 1] = calculateRGBChanges(picture[i + 1])
  1100.     end
  1101.  
  1102.     return picture
  1103. end
  1104.  
  1105. function image.invert(picture)
  1106.     for i = 1, #picture, 4 do
  1107.         picture[i] = 0xffffff - picture[i]
  1108.         picture[i + 1] = 0xffffff - picture[i + 1]
  1109.     end
  1110.     return picture
  1111. end
  1112.  
  1113. function image.photoFilter(picture, color, transparency)
  1114.     if transparency < 0 then transparency = 0 elseif transparency > 255 then transparency = 255 end
  1115.     for i = 1, #picture, 4 do
  1116.         picture[i] = image.alphaBlend(picture[i], color, transparency)
  1117.         picture[i + 1] = image.alphaBlend(picture[i + 1], color, transparency)
  1118.     end
  1119.     return picture
  1120. end
  1121.  
  1122. function image.replaceColor(picture, fromColor, toColor)
  1123.     for i = 1, #picture, 4 do
  1124.         if picture[i] == fromColor then picture[i] = toColor end
  1125.     end
  1126.     return picture
  1127. end
  1128.  
  1129. --Функция размытия по Гауссу
  1130. function image.gaussianBlur(picture, radius, force)
  1131.     --Функция для генерации матрицы размытия
  1132.     local function createConvolutionMatrix(maximumValue, matrixSize)
  1133.         local delta = maximumValue / matrixSize
  1134.         local matrix = {}
  1135.         for y = 1, matrixSize do
  1136.             for x = 1, matrixSize do
  1137.                 local value = ((x - 1) * delta + (y - 1) * delta) / 2
  1138.                 matrix[y] = matrix[y] or {}
  1139.                 matrix[y][x] = value
  1140.             end
  1141.         end
  1142.         return matrix
  1143.     end
  1144.  
  1145.     --Функция для распределения стартового цвета на указанный пиксель на основе указанного значения матрицы
  1146.     local function spreadPixelToSpecifiedCoordinates(picture, xCoordinate, yCoordinate, matrixValue, startBackground, startForeground, startAlpha, startSymbol)
  1147.         local matrixBackground, matrixForeground, matrixAlpha, matrixSymbol = image.get(picture, xCoordinate, yCoordinate)
  1148.  
  1149.         if matrixBackground and matrixForeground then
  1150.             local newBackground = image.alphaBlend(startBackground, matrixBackground, matrixValue)
  1151.             --Пизданись оно все в жопу, ебанина
  1152.             --Короч, смари. Если символ равен пробелу, то мы полюбэ не учитываем цвет текста, верно?
  1153.             --Но в будущих итерациях это цвет будет учтен, поэтому возникали ссаные баги графические
  1154.             --Поэтому даже для ебучего пробела мы присваиваем значение цвета текста, равному НОВОМУ цвету фона
  1155.             --Т.е. вроде бы как они и равны, но потом охуенно все будет, угу
  1156.             local newForeground = matrixSymbol == " " and newBackground or image.alphaBlend(startForeground, matrixForeground, matrixValue)
  1157.  
  1158.             image.set(picture, xCoordinate, yCoordinate, newBackground, newForeground, 0x00, matrixSymbol)
  1159.         end
  1160.     end
  1161.  
  1162.     --Функция, распределяющая указанный пиксель по соседним пикселям на основе матрицы
  1163.     local function spreadColorToOtherPixels(picture, xStart, yStart, matrix)
  1164.         --Получаем стартовые данные о пикселе
  1165.         local startBackground, startForeground, startAlpha, startSymbol = image.get(picture, xStart, yStart)
  1166.         local xCoordinate, yCoordinate
  1167.         --Перебираем матрицу
  1168.         for yMatrix = 1, #matrix do
  1169.             for xMatrix = 1, #matrix[yMatrix] do
  1170.                 --гнорируем стартовый пиксель, на кой хер его размывать-то?
  1171.                 if not (xMatrix == 1 and yMatrix == 1) then
  1172.                     --Получаем координаты новых пикселей в изображении
  1173.                     --в обратном направлении матрицы
  1174.                     xCoordinate, yCoordinate = xStart - xMatrix + 1, yStart - yMatrix + 1
  1175.                     spreadPixelToSpecifiedCoordinates(picture, xCoordinate, yCoordinate, matrix[yMatrix][xMatrix], startBackground, startForeground, startAlpha, startSymbol)
  1176.                     --Для начала в правильную сторону матрицы
  1177.                     xCoordinate, yCoordinate = xStart + xMatrix - 1, yStart + yMatrix - 1
  1178.                     spreadPixelToSpecifiedCoordinates(picture, xCoordinate, yCoordinate, matrix[yMatrix][xMatrix], startBackground, startForeground, startAlpha, startSymbol)
  1179.                 end
  1180.             end
  1181.         end
  1182.     end
  1183.  
  1184.     --Генерируем матрицу
  1185.     local matrix = createConvolutionMatrix(force or 0x55, radius)
  1186.     --Распределяем все пиксели по изображению
  1187.     for y = 1, picture.height do
  1188.         for x = 1, picture.width do
  1189.             spreadColorToOtherPixels(picture, x, y, matrix)
  1190.         end
  1191.     end
  1192.     return picture
  1193. end
  1194.  
  1195. ----------------------------------------- Строковая обработка изображений -------------------------------------------------------------------
  1196.  
  1197. --Преобразовать изображение в строковую интерпретацию, которая может быть вставлена в код
  1198. --Удобно, если не хочется возиться с файловой системой
  1199. function image.toString(picture)
  1200.     local stringedPicture = {}
  1201.     picture = convertImageColorsTo8Bit(picture)
  1202.     table.insert(stringedPicture, string.format("%02X", picture.width))
  1203.     table.insert(stringedPicture, string.format("%02X", picture.height))
  1204.     for i = 1, #picture, 4 do
  1205.         table.insert(stringedPicture, string.format("%02X", picture[i]))
  1206.         table.insert(stringedPicture, string.format("%02X", picture[i + 1]))
  1207.         table.insert(stringedPicture, string.format("%02X", picture[i + 2]))
  1208.         table.insert(stringedPicture, picture[i + 3])
  1209.     end
  1210.     picture = convertImageColorsTo24Bit(picture)
  1211.     return table.concat(stringedPicture)
  1212. end
  1213.  
  1214. --Получить изображение из строковой интерпретации, созданной ранее
  1215. function image.fromString(stringedPicture)
  1216.     local picture = {}
  1217.     local subIndex = 1
  1218.     picture.width = tonumber("0x" .. unicode.sub(stringedPicture, subIndex, subIndex + 1)); subIndex = subIndex + 2
  1219.     picture.height = tonumber("0x" .. unicode.sub(stringedPicture, subIndex, subIndex + 1)); subIndex = subIndex + 2
  1220.    
  1221.     for pixel = 1, picture.width * picture.height do
  1222.         table.insert(picture, tonumber("0x" .. unicode.sub(stringedPicture, subIndex, subIndex + 1))); subIndex = subIndex + 2
  1223.         table.insert(picture, tonumber("0x" .. unicode.sub(stringedPicture, subIndex, subIndex + 1))); subIndex = subIndex + 2
  1224.         table.insert(picture, tonumber("0x" .. unicode.sub(stringedPicture, subIndex, subIndex + 1))); subIndex = subIndex + 2
  1225.         table.insert(picture, unicode.sub(stringedPicture, subIndex, subIndex)); subIndex = subIndex + 1
  1226.     end
  1227.     picture = convertImageColorsTo24Bit(picture)
  1228.     return picture
  1229. end
  1230.  
  1231. ----------------------------------------- Основные функции программы -------------------------------------------------------------------
  1232.  
  1233. --Сохранить изображение любого поддерживаемого формата
  1234. function image.save(path, picture, encodingMethod)
  1235.     encodingMethod = encodingMethod or 4
  1236.     --Создать папку под файл, если ее нет
  1237.  
  1238.     --Получаем формат указанного файла
  1239.  
  1240.     --Проверяем соответствие формата файла
  1241.     if true then
  1242.         --Оптимизируем картинку
  1243.         picture = image.optimize(picture)
  1244.         --Открываем файл
  1245.         local file = io.open(path, "w")
  1246.         --Записываем сигнатуру
  1247.         writeSignature(file)
  1248.         --Разбираемся с кодировкой
  1249.         if encodingMethod == 0 or string.lower(encodingMethod) == "raw" then
  1250.             file:write(string.char(encodingMethod))
  1251.             saveRaw(file, picture)
  1252.         elseif encodingMethod == 1 or string.lower(encodingMethod) == "ocif1" then
  1253.             file:write(string.char(encodingMethod))
  1254.             saveOCIF1(file, picture)
  1255.         elseif encodingMethod == 2 or string.lower(encodingMethod) == "ocif2" then
  1256.             file:write(string.char(encodingMethod))
  1257.             saveOCIF2(file, picture)
  1258.         elseif encodingMethod == 3 or string.lower(encodingMethod) == "ocif3" then
  1259.             error("Saving in encoding method 3 is deprecated and no longer supported. Use method 4 instead of it.")
  1260.         elseif encodingMethod == 4 or string.lower(encodingMethod) == "ocif4" then
  1261.             file:write(string.char(encodingMethod))
  1262.             picture = convertImageColorsTo8Bit(picture)
  1263.             saveOCIF2(file, picture, true)
  1264.             picture = convertImageColorsTo24Bit(picture)
  1265.         elseif encodingMethod == 6 then
  1266.             file:close()
  1267.             file = io.open(path, "w")
  1268.             file:write(image.toString(picture))
  1269.             file:close()
  1270.         else
  1271.             file:close()
  1272.             error("Unsupported encoding method.\n")
  1273.         end
  1274.     end
  1275. end
  1276.  
  1277. --Загрузить изображение любого поддерживаемого формата
  1278. function image.load(path)
  1279.     --Кинуть ошибку, если такого файла не существует
  1280.     --Получаем формат указанного файла
  1281.     --Проверяем соответствие формата файла
  1282.     if true then
  1283.         local file = io.open(path, "rb")
  1284.         --Читаем сигнатуру файла
  1285.         readSignature(file)
  1286.         --Читаем метод обработки изображения
  1287.         local encodingMethod = string.byte(file:read(1))
  1288.         --Читаем файлы в зависимости от метода
  1289.         --print("Загружаю файл типа " .. encodingMethod)
  1290.         if encodingMethod == 0 then
  1291.             return image.optimize(loadRaw(file))
  1292.         elseif encodingMethod == 1 then
  1293.             return image.optimize(loadOCIF1(file))
  1294.         elseif encodingMethod == 2 then
  1295.             return image.optimize(loadOCIF2(file))
  1296.         elseif encodingMethod == 3 then
  1297.             return image.optimize(loadOCIF2(file, true))
  1298.         elseif encodingMethod == 4 then
  1299.             return image.optimize(loadOCIF2(file, true, true))
  1300.         else
  1301.             file:close()
  1302.             error("Unsupported encoding method: " .. encodingMethod .. "\n")
  1303.         end
  1304.     --Поддержка ПНГ-формата
  1305.     end
  1306. end
  1307.  
  1308. --Отрисовка изображения типа 3 (подробнее о типах см. конец файла)
  1309. function image.draw(x, y, picture)
  1310.     --Конвертируем в групповое изображение
  1311.     picture = image.convertToGroupedImage(picture)
  1312.     --Все как обычно
  1313.     x, y = x - 1, y - 1
  1314.  
  1315.     local xPos, yPos, currentBackground
  1316.     for alpha in pairs(picture) do
  1317.         for symbol in pairs(picture[alpha]) do
  1318.             for foreground in pairs(picture[alpha][symbol]) do
  1319.                 if component.gpu.getForeground ~= foreground then component.gpu.setForeground(foreground) end
  1320.                 for background in pairs(picture[alpha][symbol][foreground]) do
  1321.                     if component.gpu.getBackground ~= background then component.gpu.setBackground(background) end
  1322.                     currentBackground = background
  1323.  
  1324.                     for yArray in pairs(picture[alpha][symbol][foreground][background]) do
  1325.                         for xArray = 1, #picture[alpha][symbol][foreground][background][yArray] do
  1326.                             xPos, yPos = x + picture[alpha][symbol][foreground][background][yArray][xArray], y + yArray
  1327.                            
  1328.                             --Если альфа имеется, но она не совсем прозрачна
  1329.                             if (alpha > 0x00 and alpha < 0xFF) or (alpha == 0xFF and symbol ~= " ")then
  1330.                                 _, _, currentBackground = component.gpu.get(xPos, yPos)
  1331.                                 currentBackground = image.alphaBlend(currentBackground, background, alpha)
  1332.                                 component.gpu.setBackground(currentBackground)
  1333.  
  1334.                                 component.gpu.set(xPos, yPos, symbol)
  1335.  
  1336.                             elseif alpha == 0x00 then
  1337.                                 if currentBackground ~= background then
  1338.                                     currentBackground = background
  1339.                                     component.gpu.setBackground(currentBackground)
  1340.                                 end
  1341.  
  1342.                                 component.gpu.set(xPos, yPos, symbol)
  1343.                             end
  1344.                             --ecs.wait()
  1345.                         end
  1346.                     end
  1347.                 end
  1348.             end
  1349.         end
  1350.     end
  1351. end
  1352.  
  1353. local function createSaveAndLoadFiles()
  1354.     ecs.prepareToExit()
  1355.     ecs.error("Создаю/загружаю изображение")
  1356.     -- local cyka = image.load("MineOS/System/OS/Icons/Love.pic")
  1357.     local cyka = image.createImage(4, 4)
  1358.     ecs.error("Рисую загруженное изображение")
  1359.     image.draw(2, 2, cyka)
  1360.     ecs.error("Сохраняю его в 4 форматах")
  1361.     image.save("0.pic", cyka, 0)
  1362.     image.save("1.pic", cyka, 1)
  1363.     image.save("4.pic", cyka, 4)
  1364.     ecs.prepareToExit()
  1365.     ecs.error("Загружаю все 4 формата и рисую их")
  1366.     local cyka0 = image.load("0.pic")
  1367.     image.draw(2, 2, cyka0)
  1368.     local cyka1 = image.load("1.pic")
  1369.     image.draw(10, 2, cyka1)
  1370.     local cyka4 = image.load("4.pic")
  1371.     image.draw(34, 2, cyka4)
  1372. end
  1373.  
  1374. return image
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement