Skip to content

Commit 1b3585a

Browse files
committed
Much faster getImageDimensions() for most images.
1 parent 2b1cf7f commit 1b3585a

File tree

6 files changed

+170
-5
lines changed

6 files changed

+170
-5
lines changed
85.1 KB
Loading

examples/testsite/scripts/tests/runTests.lua

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ return function()
6363
-- print(doc:tostring())
6464
--]==]
6565

66-
-- [==[ HTML parsing.
66+
--[==[ HTML parsing.
6767
local htmlStr = (
6868
-- readTextFile"../local/test-youtube-watch-page.html" or
6969
-- readTextFile"../local/test-deviantart-front-page.html" or
@@ -102,8 +102,22 @@ return function()
102102
print(doc:toHtml())
103103
--]==]
104104

105+
-- [[ Get image dimensions.
106+
local path = "/images/sakura-trees.jpg"
107+
local w, h = assert(XXX_getImageDimensionsFast(path, true))
108+
assert(w == 510 and h == 340, "Bad size for "..path)
109+
110+
local path = "/images/head.png"
111+
local w, h = assert(XXX_getImageDimensionsFast(path, true))
112+
assert(w == 50 and h == 50, "Bad size for "..path)
113+
114+
-- local time = require"socket.core".gettime()
115+
-- for i = 1, 50 do XXX_getImageDimensionsFast(path, true) end
116+
-- print(require"socket.core".gettime()-time)
117+
--]]
118+
105119
print()
106120
print("TESTS COMPLETED!!!")
107121
print()
108-
os.exit(1)
122+
os.exit(2)
109123
end

src/app.lua2p

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,11 @@ local function setup()
755755

756756
getImageDimensions = function(sitePath)
757757
local getImageDimensions_internal = getImageDimensions -- :BetterTraceback :BetterNameForRedirectedFunctionInErrorMessage
758-
local wOrNil, hOrErr = getImageDimensions_internal(sitePathToPath(sitePath, 2))
758+
local wOrNil, hOrErr = getImageDimensions_internal(sitePathToPath(sitePath, 2), false)
759+
return wOrNil, hOrErr
760+
end,
761+
XXX_getImageDimensionsFast = function(sitePath)
762+
local wOrNil, hOrErr = getImageDimensions(sitePathToPath(sitePath, 2), true)
759763
return wOrNil, hOrErr
760764
end,
761765

src/functions.lua2p

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2858,8 +2858,27 @@ do
28582858
end
28592859
end
28602860

2861-
function _G.getImageDimensions(pathImageRel)
2862-
-- @Speed: Cache result.
2861+
-- Returns nil and a message on error.
2862+
function _G.getImageDimensions(pathImageRel, mustBeFast)
2863+
-- @Speed: Cache the result.
2864+
2865+
-- Try the faster method using our own file decoder.
2866+
local extLower = getExtension(pathImageRel):lower()
2867+
2868+
if extLower == "jpg" or extLower == "jpeg" then
2869+
local w, h = imageLib.jpegGetDimensions(DIR_CONTENT.."/"..pathImageRel)
2870+
if w then return w, h end
2871+
2872+
elseif extLower == "png" then
2873+
local w, h = imageLib.pngGetDimensions(DIR_CONTENT.."/"..pathImageRel)
2874+
if w then return w, h end
2875+
end
2876+
2877+
if mustBeFast then
2878+
return nil, F("Could not determine the dimensions of '%'.", maybeFullPath(DIR_CONTENT.."/"..pathImageRel))
2879+
end
2880+
2881+
-- Try the much slower method using GD.
28632882
local image, err = loadImage(pathImageRel)
28642883
if not image then return nil, err end
28652884

src/globals.lua2p

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ _G.NOOP = function()end
4444
_G.lfs = require"lfs"
4545

4646
_G.dateLib = require"date"
47+
_G.imageLib = require"image"
4748
_G.jsonLib = require"json"
4849
_G.markdownLib = require"markdown"
4950
_G.tomlLib = require"toml"

src/image.lua2p

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
--[[============================================================
2+
--=
3+
--= Image file parsing module
4+
--=
5+
--=-------------------------------------------------------------
6+
--=
7+
--= LuaWebGen - static website generator in Lua!
8+
--= - Written by Marcus 'ReFreezed' Thunström
9+
--= - MIT License (See LICENSE.txt)
10+
--=
11+
--==============================================================
12+
13+
jpegGetDimensions
14+
pngGetDimensions
15+
16+
--============================================================]]
17+
18+
local imageLib = {}
19+
20+
21+
22+
local function read1Byte(file)
23+
return assert(file:read(1)):byte()
24+
end
25+
26+
local function read2Bytes(file)
27+
return assert(file:read(1)):byte(),
28+
assert(file:read(1)):byte()
29+
end
30+
31+
local function read4Bytes(file)
32+
return assert(file:read(1)):byte(),
33+
assert(file:read(1)):byte(),
34+
assert(file:read(1)):byte(),
35+
assert(file:read(1)):byte()
36+
end
37+
38+
39+
40+
-- width, height = jpegGetDimensions( path )
41+
-- Returns nil on error.
42+
-- May not work on absolutely all JPEG files.
43+
-- Ported from https://stackoverflow.com/questions/4092624/parse-image-size-from-jpeg/4093050#4093050
44+
function imageLib.jpegGetDimensions(path)
45+
local file = io.open(path, "rb")
46+
if not file then return nil end
47+
48+
local ok, w, h = pcall(function()
49+
local fileLength = assert(file:seek("end"))
50+
assert(file:seek("set", 0))
51+
52+
local b1, b2, b3, b4 = read4Bytes(file)
53+
assert(b1 == 0xff and b2 == 0xd8 and b3 == 0xff and b4 == 0xe0)
54+
55+
local blockStart = assert(file:seek("cur"))
56+
57+
b1, b2 = read2Bytes(file)
58+
local blockLength = b1*256 + b2
59+
60+
b1, b2, b3, b4 = read4Bytes(file)
61+
!local B = string.byte
62+
assert(b1 == !(B"J") and b2 == !(B"F") and b3 == !(B"I") and b4 == !(B"F") and read1Byte(file) == 0)
63+
64+
blockStart = blockStart + blockLength
65+
66+
while blockStart < fileLength do
67+
assert(file:seek("set", blockStart))
68+
69+
b1, b2, b3, b4 = read4Bytes(file)
70+
blockLength = b3*256 + b4
71+
72+
if blockLength >= 7 and b1 == 0xff and b2 == 0xc0 then
73+
read1Byte(file)
74+
b1, b2, b3, b4 = read4Bytes(file)
75+
76+
local h = b1*256 + b2
77+
local w = b3*256 + b4
78+
79+
return w, h
80+
end
81+
82+
blockStart = blockStart + blockLength+2
83+
end
84+
85+
error("")
86+
end)
87+
88+
file:close()
89+
if not ok then return nil end
90+
91+
return w, h
92+
end
93+
94+
95+
96+
-- width, height = pngGetDimensions( path )
97+
-- Returns nil on error.
98+
function imageLib.pngGetDimensions(path)
99+
local file = io.open(path, "rb")
100+
if not file then return nil end
101+
102+
local ok, w, h = pcall(function()
103+
local startContents = assert(file:read(24)) -- We only need the first 24 bytes.
104+
assert(#startContents == 24)
105+
106+
-- Correct signature?
107+
assert(startContents:find("\137\80\78\71\13\10\26\10", 1, true) == 1)
108+
109+
-- Correct first chunk?
110+
assert(startContents:find("^IHDR", 13))
111+
112+
-- All good!
113+
local wb1,wb2,wb3,wb4, hb1,hb2,hb3,hb4 = startContents:byte(17, 24) -- Two big-endian 4-byte uints.
114+
return wb1*!(256^3) + wb2*!(256^2) + wb3*!(256^1) + wb4*!(256^0),
115+
hb1*!(256^3) + hb2*!(256^2) + hb3*!(256^1) + hb4*!(256^0)
116+
end)
117+
assert(ok, w)
118+
119+
file:close()
120+
if not ok then return nil end
121+
122+
return w, h
123+
end
124+
125+
126+
127+
return imageLib

0 commit comments

Comments
 (0)