bitmapin-memory bitmaps |
|
local bitmap = require'bitmap'
A bitmap is a table with the following fields:
w
, h
- bitmap dimensions, in pixels.stride
- row stride in bytes. must be at least w * bpp / 8
(can be fractional for < 8bpp formats).bottom_up
- if true
, the rows are are arranged bottom-up instead of top-down.data
- the pixel buffer (string or a cdata buffer). the pixels must be packed in stride
-long rows, top-down or bottom-up.size
- size of the pixel buffer, in bytes.format
- the pixel format, either a string naming a predefined format (below table), or a table specifying a custom format (see customization).name | colortype | channels | bits/channel | bits/pixel |
---|---|---|---|---|
rgb8, bgr8 | rgba8 | RGB | 8 | 24 |
rgb16, bgr16 | rgba16 | RGB | 16 | 48 |
rgbx8, bgrx8, xrgb8, xbgr8 | rgba8 | RGB | 8 | 32 |
rgbx16, bgrx16, xrgb16, xbgr16 | rgba16 | RGB | 16 | 64 |
rgba8, bgra8, argb8, abgr8 | rgba8 | RGB+alpha | 8 | 32 |
rgba16, bgra16, argb16, abgr16 | rgba16 | RGB+alpha | 16 | 64 |
rgb565 | rgba8 | RGB | 5/6/5 | 16 |
rgb555 | rgba8 | RGB | 5 | 16 |
rgb444 | rgba8 | RGB | 4 | 16 |
rgba4444 | rgba8 | RGB+alpha | 4 | 16 |
rgba5551 | rgba8 | RGB+alpha | 5/5/5/1 | 16 |
ga8, ag8 | ga8 | GRAY+alpha | 8 | 16 |
ga16, ag16 | ga16 | GRAY+alpha | 16 | 32 |
g1 | ga8 | GRAY | 1 | 1 |
g2 | ga8 | GRAY | 2 | 2 |
g4 | ga8 | GRAY | 4 | 4 |
g8 | ga8 | GRAY | 8 | 8 |
g16 | ga16 | GRAY | 16 | 16 |
cmyk8 | cmyk8 | inverse CMYK | 8 | 32 |
ycc8 | ycc8 | JPEG YCbCr 8 | 8 | 24 |
ycck8 | ycck8 | JPEG YCbCrK 8 | 8 | 32 |
rgbaf | rgbaf | RGB+alpha | 32 | 128 |
rgbad | rgbaf | RGB+alpha | 64 | 256 |
name | channels | value type | value range |
---|---|---|---|
rgba8 | r, g, b, a | integer | 0..0xff |
rgba16 | r, g, b, a | integer | 0..0xffff |
ga8 | g, a | integer | 0..0xff |
ga16 | g, a | integer | 0..0xffff |
cmyk8 | c, m, y, k | integer | 0..0xff |
ycc8 | y, c, c | integer | 0..0xff |
ycck8 | y, c, c, k | integer | 0..0xff |
rgbaf | r, g, b, a | float or double | 0..1 |
format conversion | |
bitmap.new(w, h, ...) -> dst |
create a bitmap |
bitmap.copy(src[, format], ...) -> dst |
copy and convert a bitmap |
bitmap.paint(src, dst, ...) -> dst |
paint a bitmap |
cropping | |
bitmap.sub(src, x, y, w, h) -> dst |
make a sub-bitmap |
pixel access | |
bitmap.pixel_interface(src) -> getpixel, setpixel |
get a pixel interface |
bitmap.channel_interface(bmp, n) -> getval, setval |
get a channel interface |
dithering | |
bitmap.dither.fs|ordered(bmp, rN, gN, bN, aN) |
apply dithering |
effects | |
bitmap.invert(bmp) |
invert colors |
bitmap.grayscale(bmp) |
desaturate |
bitmap.convolve(src, kernel, [edge]) -> dst |
convolve |
bitmap.sharpen(src[, threshold]) -> dst |
sharpen |
alpha blending | |
bitmap.blend(src, dst, [operator], [x], [y]) |
blend source into dest bitmap |
resizing | |
bitmap.resize.nearest|bilinear(src, w, h) -> dst |
resize to new |
bitmap.resize.nearest|bilinear(src, dst) -> dst |
resize to dest |
utilities | |
bitmap.min_stride(format, width) -> min_stride |
minimum stride for width |
bitmap.aligned_stride(stride) -> aligned_stride |
minimum stride that is aligned |
bitmap.row_size(bmp) -> size |
row size in bytes |
bitmap.new(w, h, format, [bottom_up], [stride_aligned], [stride]) -> new_bmp
Create a bitmap object. If stride_aligned
is true
and no specific stride
is given, the stride will be a multiple of 4 bytes.
bitmap.copy(bmp, [format], [bottom_up], [stride_aligned], [stride]) -> new_bmp
Copy a bitmap, optionally to a new format, orientation and stride. If format
is not specified, stride and orientation default to those of source bitmap's, otherwise they default to top-down, minimum stride.
bitmap.paint(source_bmp, dest_bmp[, convert_pixel]) -> dest_bmp
Paint a source bitmap into a destination bitmap of the same width and height, with all the necessary pixel and colortype conversions.
The optional convert_pixel
is a pixel conversion function to be called for each pixel as convert_pixel(a, b, c, ...) -> x, y, z, ...
. It receives the channel values of the source bitmap according to its colortype and must return the converted channel values for the destination bitmap according to its colortype.
In some cases, the destination bitmap is allowed to have the same data buffer as the source bitmap. Specifically, it must have the same orientation, smaller or equal stride and smaller or equal pixel size. The destination bitmap can also be the source bitmap itself, which is useful for performing custom transformations via the convert_pixel
callback.
bitmap.sub(bmp, [x], [y], [w], [h]) -> sub_bmp
Crop a bitmap without copying the pixels (the data
field of the sub-bitmap is a pointer into the data
buffer of the parent bitmap). The parent bitmap is pinned in the parent
field of the sub-bitmap to prevent garbage collection of the data buffer. Other than that, the sub-bitmap behaves exactly like a normal bitmap (it can be further sub'ed for instance). The coordinates default to 0, 0, bmp.w, bmp.h
respectively. The coordinates are adjusted to fit the parent bitmap. If they result in zero width or height, nothing is returned.
To get real cropping, just copy the bitmap, specifying the format and orientation to reset the stride:
sub = bitmap.copy(sub, sub.format, sub.bottom_up)
NOTE: For 1, 2, 4 bpp formats, the coordinates must be such that the data pointer points to the beginning of a byte (that is, is not fractional). For a non-fractional stride, this means the
x
coordinate must be a multiple of 8, 4, 2 respectively. For fractional strides don't even bother.
bitmap.pixel_interface(bitmap[, colortype]) -> getpixel, setpixel
Return an API for getting and setting individual pixels of a bitmap object:
getpixel(x, y) -> a, b, c, ...
setpixel(x, y, a, b, c, ...)
where a, b, c are the individual color channels, converted to the specified colortype or in the colortype of the bitmap (i.e. r, g, b, a for the 'rgba' colortype, etc.).
local function darken(r, g, b, a)
return r / 2, g / 2, b / 2, a / 2) --make 2x darker
end
local getpixel, setpixel = bitmap.pixel_interface(bmp)
for y = 0, bmp.h-1 do
for x = 0, bmp.w-1 do
setpixel(x, y, darken(getpixel(x, y)))
end
end
--the above has the same effect as:
bitmap.convert(bmp, bmp, darken)
bitmap.channel_interface(bitmap, channel) -> getvalue, setvalue
Return an API for getting and setting values from a single color channel:
getvalue(x, y) -> v
setvalue(x, y, v)
bitmap.dither.fs(bmp, rbits, gbits, bbits, abits)
Dither a bitmap using the Floyd-Steinberg dithering algorithm. *bits
specify the number of bits of color to keep for each channel (eg. bitmap.dither.fs(bmp, 5, 6, 5, 0)
dithers a bitmap so that its colors fit into the rgb565
format). Only implemented for 4-channel colortypes.
bitmap.dither.ordered(bmp, mapsize)
Dither a bitmap using the ordered dithering algorithm. mapsize
specifies the threshold map to use and can be 2, 3, 4 or 8. Use the demo to see how this parameter affects the output quality depending on the output format (it's not a clear-cut choice). Implemented for 2-channel and 4-channel colortypes. Note that actual clipping of the low bits is not done, it will be done naturally when converting the bitmap to a lower bit depth.
bitmap.invert(bmp)
Invert colors.
bitmap.grayscale(bmp)
Convert pixels to grayscale, without changing the format.
bitmap.convolve(bmp, kernel, [edge]) -> new_bmp
Convolve a bitmap using a kernel matrix (a Lua array of arrays of the same length). edge
can be crop
, wrap
or extend
(default is extend
).
bitmap.sharpen(bmp[, threshold]) -> new_bmp
Sharpen a bitmap.
bitmap.blend(source_bmp, dest_bmp, [operator], [x], [y])
Blend source_bmp
into dest_bmp
using a blending operator at x,y
coordinates in the target bitmap (default is 0,0
). Operators are in the bitmap.blend_op
table for inspection.
Painting a bitmap onto another at specific coordinates, based on bitmap.fit and sub-bitmaps:
local box2d = require'box2d'
function paint_at_xy(src, dst, x, y)
local x, y, w, h = box2d.clip(x, y, src.w, src.h, 0, 0, dst.w, dst.h)
local src = bitmap.sub(src, 0, 0, w, h)
local dst = bitmap.sub(dst, x, y, w, h)
if src and dst then
bitmap.paint(src, dst)
end
end
bitmap.resize.<method>(bmp, w, h) -> new_bmp
bitmap.resize.<method>(source_bmp, dest_bmp) -> dest_bmp
Resize a bitmap. The method can be nearest
or bilinear
.
bitmap.min_stride(format, width) -> min_stride
Return the minimum stride in bytes given a format and width. A bitmap data buffer should never be smaller than min_stride * height
.
bitmap.aligned_stride(stride) -> aligned_stride
Given a stride, return the smallest stride that is a multiple of 4 bytes.
bitmap.row_size(bmp) -> size
Bitmap's row size, in bytes, i.e. bitmap's minimum stride.
bitmap.conversions(source_format) -> iter() -> name, def
Given a source bitmap format, iterate through all the formats that the source format can be converted to. name
is the format name and def
is the format definition which is a table with the fields bpp
, ctype
, colortype
, read
, write
.
bitmap.dumpinfo()
Print the list of supported pixel formats and the list of supported colortype conversions.
Extending the bitmap
module with new colortypes, formats, conversions and module functions is easy. Look at the bitmap_rgbaf
sub-module for an example on how to do that. For the submodule to be loaded automatically you need to reference it in the bitmap
module too in a few key spots. Again, look at how rgbaf
does it.
A custom pixel format definition is a table with the following fields:
bpp
- pixel size, in bits (must be an even number of bits).ctype
- C type to cast data
to when reading and writing pixels (see below).colortype
- a string naming a standard color type or a table specifying a custom color type. The channel values that read
and write
refer to depend on the colortype, eg. for the 'rgba8' colortype, the read function must return 4 numbers in the 0-255 range corresponding to the R, G, B, A channels.read
- a function to be called as read(data, i) -> a, b, c, ...
; the function must decode the pixel at data[i]
and return its channel values according to colortype.write
- a function to be called as write(data, i, a, b, c, ...)
; the function must encode the given channel values according to colortype and write the pixel at data[i]
.
bit.band(i * 8, 7)
.A custom colortype definition is a table with the following fields:
channels
- a string with each letter a channel name, eg. 'rgba', so that #channels
indicates the number of channels.max
- maximum value to which the channel values need to be clipped.bpc
- bits/channel - same meaning as max
but in bits.