summaryrefslogtreecommitdiffstats
path: root/apps/plugins/lua/include_lua/image_save.lua
blob: 4735af46d7ebc1555a6fb2632ae8517a9c9e4f74 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
--[[ Lua Image save
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2017 William Wilgus
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
]]
-- save(img, path/name)
-- bmp saving derived from rockbox - screendump.c
-- bitdepth is limited by the device
-- eg. device displays greyscale, rgb images are saved greyscale
if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end

do
    local rocklib_image = getmetatable(rb.lcd_framebuffer())

    -- internal constants
    local _NIL = nil -- _NIL placeholder
    local _points  = rocklib_image.points

    -- saves img to file: name
    return function(img, name)
        local file
        local bbuffer = {} -- concat buffer for s_bytes
        local fbuffer = {} -- concat buffer for file writes, reused

        local function s_bytesLE(bits, value)
            -- bits must be multiples of 8 (sizeof byte)
            local byte
            local nbytes = bit.rshift(bits, 3)
            for b = 1, nbytes do
                if value > 0 then
                    byte  = value % 256
                    value = (value - byte) / 256
                else
                    byte = 0
                end
                bbuffer[b] = string.char(byte)
            end
            return table.concat(bbuffer, _NIL, 1, nbytes)
        end

        local function s_bytesBE(bits, value)
            -- bits must be multiples of 8 (sizeof byte)
            local byte
            local nbytes = bit.rshift(bits, 3)
            for b = nbytes, 1, -1 do
                if value > 0 then
                    byte  = value % 256
                    value = (value - byte) / 256
                else
                    byte = 0
                end
                bbuffer[b] = string.char(byte)
            end
            return table.concat(bbuffer, _NIL, 1, nbytes)
        end

        local cmp = {["r"] =  function(c) return bit.band(bit.rshift(c, 16), 0xFF) end,
                     ["g"] =  function(c) return bit.band(bit.rshift(c, 08), 0xFF) end,
                     ["b"] =  function(c) return bit.band(c, 0xFF) end}

        local function bmp_color(color)
            return s_bytesLE(8, cmp.b(color))..
                   s_bytesLE(8, cmp.g(color))..
                   s_bytesLE(8, cmp.r(color))..
                   s_bytesLE(8, 0) .. ""
        end -- c_cmp(color, c.r))

        local function bmp_color_mix(c1, c2, num, den)
            -- mixes c1 and c2 as ratio of numerator / denominator
            -- used 2x each save results
            local bc1, gc1, rc1 = cmp.b(c1), cmp.g(c1), cmp.r(c1)

            return s_bytesLE(8, cmp.b(c2) - bc1 * num / den + bc1)..
                   s_bytesLE(8, cmp.g(c2) - gc1 * num / den + gc1)..
                   s_bytesLE(8, cmp.r(c2) - rc1 * num / den + rc1)..
                   s_bytesLE(8, 0) .. ""
        end

        local w, h = img:width(), img:height()
        local depth  = tonumber(img:__tostring(6)) -- RLI_INFO_DEPTH   = 0x6
        local format = tonumber(img:__tostring(7)) -- RLI_INFO_FORMAT  = 0x7

        local bpp, bypl -- bits per pixel, bytes per line
        -- bypl, pad rows to a multiple of 4 bytes
        if depth <= 4 then
            bpp = 8 -- 256 color image
            bypl = (w + 3)
        elseif depth <= 16 then
            bpp = 16
            bypl = (w * 2 + 3)
        elseif depth <= 24 then
            bpp = 24
            bypl = (w * 3 + 3)
        else
            bpp = 32
            bypl = (w * 4 + 3)
        end

        local linebytes = bit.band(bypl, bit.bnot(3))

        local bytesperpixel = bit.rshift(bpp, 3)
        local headersz = 54
        local imgszpad = h * linebytes

        local compression, n_colors = 0, 0
        local h_ppm, v_ppm = 0x00000EC4, 0x00000EC4 --Pixels Per Meter ~ 96 dpi

        if depth == 16 then
            compression = 3 -- BITFIELDS
            n_colors    = 3
        elseif depth <= 8 then
            n_colors = bit.lshift(1, depth)
        end

        headersz = headersz + (4 * n_colors)

        file = io.open('/' .. name, "w+") -- overwrite, rb ignores the 'b' flag

        if not file then
            rb.splash(rb.HZ, "Error opening /" .. name)
            return
        end
        -- create a bitmap header 'rope' with image details -- concatenated at end
        local bmpheader = fbuffer

              bmpheader[01] = "BM"
              bmpheader[02] = s_bytesLE(32, headersz + imgszpad)
              bmpheader[03] = "\0\0\0\0" -- WORD reserved 1 & 2
              bmpheader[04] = s_bytesLE(32, headersz) -- BITMAPCOREHEADER size
              bmpheader[05] = s_bytesLE(32, 40) -- BITMAPINFOHEADER size

              bmpheader[06] = s_bytesLE(32, w)
              bmpheader[07] = s_bytesLE(32, h)
              bmpheader[08] = "\1\0" -- WORD color planes ALWAYS 1
              bmpheader[09] = s_bytesLE(16, bpp) -- bits/pixel
              bmpheader[10] = s_bytesLE(32, compression)
              bmpheader[11] = s_bytesLE(32, imgszpad)
              bmpheader[12] = s_bytesLE(32, h_ppm) -- biXPelsPerMeter
              bmpheader[13] = s_bytesLE(32, v_ppm) -- biYPelsPerMeter
              bmpheader[14] = s_bytesLE(32, n_colors)
              bmpheader[15] = s_bytesLE(32, n_colors)

        -- Color Table (#n_colors entries)
        if depth == 1 then -- assuming positive display
            bmpheader[#bmpheader + 1] = bmp_color(0xFFFFFF)
            bmpheader[#bmpheader + 1] = bmp_color(0x0)
        elseif depth == 2 then
            bmpheader[#bmpheader + 1] = bmp_color(0xFFFFFF)
            bmpheader[#bmpheader + 1] = bmp_color_mix(0xFFFFFF, 0, 1, 3)
            bmpheader[#bmpheader + 1] = bmp_color_mix(0xFFFFFF, 0, 2, 3)
            bmpheader[#bmpheader + 1] = bmp_color(0x0)
        elseif depth == 16 then
            if format == 555 then
                -- red bitfield mask
                bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x00007C00)
                -- green bitfield mask
                bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x000003E0)
                -- blue bitfield mask
                bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x0000001F)
			else --565
                -- red bitfield mask
                bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x0000F800)
                -- green bitfield mask
                bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x000007E0)
                -- blue bitfield mask
                bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x0000001F)
            end
        end

        file:write(table.concat(fbuffer))-- write the header to the file now
        for i=1, #fbuffer do fbuffer[i] = _NIL end -- reuse table

        local imgdata = fbuffer
        -- pad rows to a multiple of 4 bytes
        local bytesleft = linebytes - (bytesperpixel * w)
        local t_data = {}
        local fs_bytes_E = s_bytesLE -- default save in Little Endian

        if format == 3553 then -- RGB565SWAPPED
            fs_bytes_E = s_bytesBE -- Saves in Big Endian
        end

        -- Bitmap lines start at bottom unless biHeight is negative
        for point in _points(img, 1, h, w + bytesleft, 1) do
            imgdata[#imgdata + 1] = fs_bytes_E(bpp, point or 0)

            if #fbuffer >= 31 then -- buffered write, increase # for performance
                file:write(table.concat(fbuffer))
                for i=1, #fbuffer do fbuffer[i] = _NIL end -- reuse table
            end

        end
        file:write(table.concat(fbuffer)) --write leftovers to file
        fbuffer = _NIL

        file:close()
    end -- save(img, name)
end