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 216 217 218
|
local m = {
INVERT = 1,
DOWNSCALE = 2,
FLIP_H = 3,
FLIP_V = 4,
ROTATE_CW = 5,
ROTATE_CCW = 6,
TRANSPARENT = 7,
TRIM = 8,
SPLIT_V = 9,
SPLIT_H = 10,
}
function initUi()
app.registerUi({ ["menu"] = "Invert selected images", ["callback"] = "Cb", ["mode"] = m.INVERT })
app.registerUi({
["menu"] = "Downscale resolution of selected images by factor 0.5",
["callback"] = "Cb",
["mode"] = m.DOWNSCALE
})
app.registerUi({ ["menu"] = "Flip selected images horizontally", ["callback"] = "Cb", ["mode"] = m.FLIP_H })
app.registerUi({ ["menu"] = "Flip selected images vertically", ["callback"] = "Cb", ["mode"] = m.FLIP_V })
app.registerUi({
["menu"] = "Rotate selected images 90 degree clockwise",
["callback"] = "Cb",
["mode"] = m.ROTATE_CW
})
app.registerUi({
["menu"] = "Rotate selected images 90 degree counterclockwise",
["callback"] = "Cb",
["mode"] = m.ROTATE_CCW
})
app.registerUi({ ["menu"] = "Make white pixels transparent", ["callback"] = "Cb", ["mode"] = m.TRANSPARENT })
app.registerUi({ ["menu"] = "Trim selected images", ["callback"] = "Cb", ["mode"] = m.TRIM })
app.registerUi({
["menu"] = "Split images vertically by largest white block",
["callback"] = "Split",
["mode"] = m.SPLIT_V
})
app.registerUi({
["menu"] = "Split images horizontally by largest white block",
["callback"] = "Split",
["mode"] = m.SPLIT_H
})
end
local function checkVips()
local hasVips, vips = pcall(require, "vips")
if not hasVips then
app.openDialog(
"You need to have lua-vips installed and included in your Lua package path in order to use this plugin. \n" ..
"Use luarocks install lua-vips and see https://github.com/libvips/lua-vips for further information.\n\n",
{ "OK" },
"", true)
return nil
end
return vips
end
function Cb(mode)
local vips = checkVips()
if not vips then return end
local images = app.getImages("selection")
local imdata = {}
local refs = {}
for i = 1, #images do
local im = images[i]
local image = vips.Image.new_from_buffer(im["data"])
local x, y, maxWidth, maxHeight = im["x"], im["y"], math.ceil(im["width"]), math.ceil(im["height"])
refs[i] = im.ref
if mode == m.INVERT then
image = image:invert()
elseif mode == m.DOWNSCALE then
image = image:resize(0.5)
elseif mode == m.FLIP_H then
image = image:flip("horizontal")
elseif mode == m.FLIP_V then
image = image:flip("vertical")
elseif mode == m.ROTATE_CW then
image = image:rot("d90")
maxWidth, maxHeight = maxHeight, maxWidth
elseif mode == m.ROTATE_CCW then
image = image:rot("d270")
maxWidth, maxHeight = maxHeight, maxWidth
elseif mode == m.TRANSPARENT then
if image:bands() == 3 then
image = image:bandjoin(255) -- add alpha channel
end
local white = { 240, 240, 240, -1 } -- pixels are considered white enough, if its r,g,b values are >240
local transparent = { 0, 0, 0, 0 }
local filter = image:more(white) -- make pixels white if they are white enough, and black otherwise
:bandand() -- joins bands with bitwise and into a single band, so values are
-- 255 (true) for white enough pixels,
-- 0 (false) otherwise
image = filter:ifthenelse(transparent, image) -- now white enough pixels become transparent, other are taken from image
elseif mode == m.TRIM then
local width = image:width()
local height = image:height()
image = image:crop(image:find_trim())
local newWidth = image:width() -- now newWidth / width is the factor by which the pixbuf got smaller
local newHeight = image:height() -- and newHeight / height is the factor by which the pixbuf height got smaller
maxWidth, maxHeight = maxWidth * newWidth // width, maxHeight * newHeight // height
end
table.insert(imdata, {
data = image:write_to_buffer(".png"),
x = x,
y = y,
maxWidth = maxWidth,
maxHeight = maxHeight
})
end
app.clearSelection()
app.addToSelection(refs)
app.activateAction("delete")
app.addImages({ images = imdata, allowUndoRedoAction = "grouped" })
end
function Split(mode)
PADDING = 15 -- margin between image parts
local ffi = require "ffi"
local vips = checkVips()
if not vips then return end
local vertical = mode == m.SPLIT_V
local images = app.getImages("selection")
local imdata = {}
local refs = {}
for i = 1, #images do
local im = images[i]
local image = vips.Image.new_from_buffer(im["data"])
local x, y, maxWidth, maxHeight = im["x"], im["y"], math.ceil(im["width"]), math.ceil(im["height"])
refs[i] = im.ref
-- find largest sequence of white or transparent pixel rows/columns
local filter = image:colourspace("b-w") -- convert image to grayscale colour space
:extract_band(0) -- ignore alpha channel if it exists
:cast("uint") -- cast from double to uint data type
:more(240) -- replace value by 255 if it is > 240 (whiteish), 0 otherwise
local width, height = filter:width(), filter:height()
local last = vertical and height or width
local colSums, rowSums = filter:project() -- width x 1 column sums, 1 x height row sums
-- now cast rowSums (or colSums) into an array for fast access
local sum = ffi.cast(ffi.typeof("unsigned int*"), vertical and rowSums:write_to_memory() or colSums:write_to_memory())
local maxSum = vertical and 255 * width or 255 * height
local whiteEnds = {}
local whiteLengths = {}
local firstAfter = false
local currentLength = 0
local n = 0
for j = 1, last do
if sum[j - 1] == maxSum then -- completely white row (or column)
if currentLength > 0 then
currentLength = currentLength + 1
else
currentLength = 1
firstAfter = true
end
else
if firstAfter then
whiteEnds[n] = j - 1
whiteLengths[n] = currentLength
n = n + 1
firstAfter = false
currentLength = 0
end
end
end
whiteEnds[0] = nil
whiteLengths[0] = nil
if #whiteEnds == 0 then
print("Could not find a split")
table.insert(imdata, { data = im["data"], x = x, y = y, maxHeight = maxHeight, maxWidth = maxWidth })
goto continue
end
local maxBlockLength = 0
local endIndex = 0
for j = 1, #whiteEnds do
if whiteLengths[j] > maxBlockLength then
maxBlockLength = whiteLengths[j]
endIndex = whiteEnds[j]
end
end
if vertical then
local height1 = endIndex - maxBlockLength // 2 -- white block is split equally
local height2 = height - height1
local image1 = image:extract_area(0, 0, width, height1)
local image2 = image:extract_area(0, height1, width, height2)
table.insert(imdata, { data = image1:write_to_buffer(".png"), x = x, y = y, maxWidth = maxWidth })
table.insert(imdata, {
data = image2:write_to_buffer(".png"),
maxWidth = maxWidth,
x = x,
y = y + height1 * maxHeight // height + PADDING, -- maxHeight / height scales from pixbuf height to height in document
})
else
local width1 = endIndex - maxBlockLength // 2 -- white block is split equally
local width2 = width - width1
local image1 = image:extract_area(0, 0, width1, height)
local image2 = image:extract_area(width1, 0, width2, height)
table.insert(imdata, { data = image1:write_to_buffer(".png"), x = x, y = y, maxHeight = maxHeight })
table.insert(imdata, {
data = image2:write_to_buffer(".png"),
maxHeight = maxHeight,
x = x + width1 * maxWidth // width + PADDING, -- maxWidth / width scales from pixbuf width to width in document
y = y,
})
end
::continue::
end
app.clearSelection()
app.addToSelection(refs)
app.activateAction("delete")
app.addImages({ images = imdata, allowUndoRedoAction = "grouped" })
end
|