Skip to content

Commit 6199b84

Browse files
Add webp support to SimpleImageInfo
1 parent 5fefbae commit 6199b84

1 file changed

Lines changed: 155 additions & 123 deletions

File tree

  • image-generation-server/src/main/kotlin/net/perfectdreams/gabrielaimageserver/webserver/utils
Lines changed: 155 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
package net.perfectdreams.gabrielaimageserver.webserver.utils
2-
31
/*
42
* SimpleImageInfo.java
53
*
@@ -29,131 +27,165 @@ package net.perfectdreams.gabrielaimageserver.webserver.utils
2927
* -------------------------------------------------------------------------------
3028
*/
3129

30+
package net.perfectdreams.gabrielaimageserver.webserver.utils
31+
3232
import java.io.*
3333

3434
class SimpleImageInfo {
35-
var height: Int = 0
36-
var width: Int = 0
37-
var mimeType: String? = null
38-
39-
private constructor()
40-
41-
@Throws(IOException::class)
42-
constructor(file: File) {
43-
val `is` = FileInputStream(file)
44-
try {
45-
processStream(`is`)
46-
} finally {
47-
`is`.close()
48-
}
49-
}
50-
51-
@Throws(IOException::class)
52-
constructor(`is`: InputStream) {
53-
processStream(`is`)
54-
}
35+
var height: Int = 0
36+
var width: Int = 0
37+
var mimeType: String? = null
5538

56-
@Throws(IOException::class)
57-
constructor(bytes: ByteArray) {
58-
val `is` = ByteArrayInputStream(bytes)
59-
try {
60-
processStream(`is`)
61-
} finally {
62-
`is`.close()
63-
}
64-
}
65-
66-
@Throws(IOException::class)
67-
private fun processStream(`is`: InputStream) {
68-
val c1 = `is`.read()
69-
val c2 = `is`.read()
70-
var c3 = `is`.read()
71-
72-
mimeType = null
73-
height = -1
74-
width = height
75-
76-
if (c1 == 'G'.toInt() && c2 == 'I'.toInt() && c3 == 'F'.toInt()) { // GIF
77-
`is`.skip(3)
78-
width = readInt(`is`, 2, false)
79-
height = readInt(`is`, 2, false)
80-
mimeType = "image/gif"
81-
} else if (c1 == 0xFF && c2 == 0xD8) { // JPG
82-
while (c3 == 255) {
83-
val marker = `is`.read()
84-
val len = readInt(`is`, 2, true)
85-
if (marker == 192 || marker == 193 || marker == 194) {
86-
`is`.skip(1)
87-
height = readInt(`is`, 2, true)
88-
width = readInt(`is`, 2, true)
89-
mimeType = "image/jpeg"
90-
break
91-
}
92-
`is`.skip((len - 2).toLong())
93-
c3 = `is`.read()
94-
}
95-
} else if (c1 == 137 && c2 == 80 && c3 == 78) { // PNG
96-
`is`.skip(15)
97-
width = readInt(`is`, 2, true)
98-
`is`.skip(2)
99-
height = readInt(`is`, 2, true)
100-
mimeType = "image/png"
101-
} else if (c1 == 66 && c2 == 77) { // BMP
102-
`is`.skip(15)
103-
width = readInt(`is`, 2, false)
104-
`is`.skip(2)
105-
height = readInt(`is`, 2, false)
106-
mimeType = "image/bmp"
107-
} else {
108-
val c4 = `is`.read()
109-
if (c1 == 'M'.toInt() && c2 == 'M'.toInt() && c3 == 0 && c4 == 42 || c1 == 'I'.toInt() && c2 == 'I'.toInt() && c3 == 42 && c4 == 0) { //TIFF
110-
val bigEndian = c1 == 'M'.toInt()
111-
var ifd = 0
112-
val entries: Int
113-
ifd = readInt(`is`, 4, bigEndian)
114-
`is`.skip((ifd - 8).toLong())
115-
entries = readInt(`is`, 2, bigEndian)
116-
for (i in 1..entries) {
117-
val tag = readInt(`is`, 2, bigEndian)
118-
val fieldType = readInt(`is`, 2, bigEndian)
119-
val count = readInt(`is`, 4, bigEndian).toLong()
120-
val valOffset: Int
121-
if (fieldType == 3 || fieldType == 8) {
122-
valOffset = readInt(`is`, 2, bigEndian)
123-
`is`.skip(2)
124-
} else {
125-
valOffset = readInt(`is`, 4, bigEndian)
126-
}
127-
if (tag == 256) {
128-
width = valOffset
129-
} else if (tag == 257) {
130-
height = valOffset
131-
}
132-
if (width != -1 && height != -1) {
133-
mimeType = "image/tiff"
134-
break
135-
}
136-
}
137-
}
138-
}
139-
if (mimeType == null) {
140-
throw IOException("Unsupported image type")
141-
}
142-
}
39+
private constructor()
14340

14441
@Throws(IOException::class)
145-
private fun readInt(`is`: InputStream, noOfBytes: Int, bigEndian: Boolean): Int {
146-
var ret = 0
147-
var sv = if (bigEndian) (noOfBytes - 1) * 8 else 0
148-
val cnt = if (bigEndian) -8 else 8
149-
for (i in 0 until noOfBytes) {
150-
ret = ret or (`is`.read() shl sv)
151-
sv += cnt
152-
}
153-
return ret
154-
}
155-
156-
override fun toString(): String {
157-
return "MIME Type : $mimeType\t Width : $width\t Height : $height"
158-
}
42+
constructor(file: File) {
43+
val `is` = FileInputStream(file)
44+
try {
45+
processStream(`is`)
46+
} finally {
47+
`is`.close()
48+
}
49+
}
50+
51+
@Throws(IOException::class)
52+
constructor(`is`: InputStream) {
53+
processStream(`is`)
54+
}
55+
56+
@Throws(IOException::class)
57+
constructor(bytes: ByteArray) {
58+
val `is` = ByteArrayInputStream(bytes)
59+
try {
60+
processStream(`is`)
61+
} finally {
62+
`is`.close()
63+
}
64+
}
65+
66+
@Throws(IOException::class)
67+
private fun processStream(`is`: InputStream) {
68+
val c1 = `is`.read()
69+
val c2 = `is`.read()
70+
var c3 = `is`.read()
71+
72+
mimeType = null
73+
height = -1
74+
width = height
75+
76+
if (c1 == 'G'.toInt() && c2 == 'I'.toInt() && c3 == 'F'.toInt()) { // GIF
77+
`is`.skip(3)
78+
width = readInt(`is`, 2, false)
79+
height = readInt(`is`, 2, false)
80+
mimeType = "image/gif"
81+
} else if (c1 == 0xFF && c2 == 0xD8) { // JPG
82+
while (c3 == 255) {
83+
val marker = `is`.read()
84+
val len = readInt(`is`, 2, true)
85+
if (marker == 192 || marker == 193 || marker == 194) {
86+
`is`.skip(1)
87+
height = readInt(`is`, 2, true)
88+
width = readInt(`is`, 2, true)
89+
mimeType = "image/jpeg"
90+
break
91+
}
92+
`is`.skip((len - 2).toLong())
93+
c3 = `is`.read()
94+
}
95+
} else if (c1 == 137 && c2 == 80 && c3 == 78) { // PNG
96+
`is`.skip(15)
97+
width = readInt(`is`, 2, true)
98+
`is`.skip(2)
99+
height = readInt(`is`, 2, true)
100+
mimeType = "image/png"
101+
} else if (c1 == 66 && c2 == 77) { // BMP
102+
`is`.skip(15)
103+
width = readInt(`is`, 2, false)
104+
`is`.skip(2)
105+
height = readInt(`is`, 2, false)
106+
mimeType = "image/bmp"
107+
} else {
108+
val c4 = `is`.read()
109+
if (c1 == 'R'.code && c2 == 'I'.code && c3 == 'F'.code && c4 == 'F'.code) {
110+
// Image in RIFF format
111+
val fileSize = `is`.readNBytes(4)
112+
val header = `is`.readNBytes(4)
113+
if (header[0].toInt() == 'W'.code && header[1].toInt() == 'E'.code && header[2].toInt() == 'B'.code && header[3].toInt() == 'P'.code) {
114+
mimeType = "image/webp"
115+
val chunkFourCC = `is`.readNBytes(4)
116+
val chunkSize = readInt(`is`, 4, false) // little-endian, not used
117+
val fourCC = String(chunkFourCC.map { it.toInt().toChar() }.toCharArray())
118+
when (fourCC) {
119+
"VP8 " -> {
120+
// Lossy format
121+
`is`.skip(3) // frame tag
122+
`is`.skip(3) // signature 0x9D 0x01 0x2A
123+
width = readInt(`is`, 2, false) and 0x3FFF
124+
height = readInt(`is`, 2, false) and 0x3FFF
125+
}
126+
"VP8L" -> {
127+
// Lossless format
128+
`is`.skip(1) // signature 0x2F
129+
val bits = readInt(`is`, 4, false)
130+
width = (bits and 0x3FFF) + 1
131+
height = ((bits shr 14) and 0x3FFF) + 1
132+
}
133+
"VP8X" -> {
134+
// Extended format
135+
`is`.skip(4) // flags
136+
width = readInt(`is`, 3, false) + 1
137+
height = readInt(`is`, 3, false) + 1
138+
}
139+
}
140+
}
141+
} else if (c1 == 'M'.toInt() && c2 == 'M'.toInt() && c3 == 0 && c4 == 42 || c1 == 'I'.toInt() && c2 == 'I'.toInt() && c3 == 42 && c4 == 0) { //TIFF
142+
val bigEndian = c1 == 'M'.toInt()
143+
var ifd = 0
144+
val entries: Int
145+
ifd = readInt(`is`, 4, bigEndian)
146+
`is`.skip((ifd - 8).toLong())
147+
entries = readInt(`is`, 2, bigEndian)
148+
for (i in 1..entries) {
149+
val tag = readInt(`is`, 2, bigEndian)
150+
val fieldType = readInt(`is`, 2, bigEndian)
151+
val count = readInt(`is`, 4, bigEndian).toLong()
152+
val valOffset: Int
153+
if (fieldType == 3 || fieldType == 8) {
154+
valOffset = readInt(`is`, 2, bigEndian)
155+
`is`.skip(2)
156+
} else {
157+
valOffset = readInt(`is`, 4, bigEndian)
158+
}
159+
if (tag == 256) {
160+
width = valOffset
161+
} else if (tag == 257) {
162+
height = valOffset
163+
}
164+
if (width != -1 && height != -1) {
165+
mimeType = "image/tiff"
166+
break
167+
}
168+
}
169+
}
170+
}
171+
if (mimeType == null) {
172+
throw IOException("Unsupported image type")
173+
}
174+
}
175+
176+
@Throws(IOException::class)
177+
private fun readInt(`is`: InputStream, noOfBytes: Int, bigEndian: Boolean): Int {
178+
var ret = 0
179+
var sv = if (bigEndian) (noOfBytes - 1) * 8 else 0
180+
val cnt = if (bigEndian) -8 else 8
181+
for (i in 0 until noOfBytes) {
182+
ret = ret or (`is`.read() shl sv)
183+
sv += cnt
184+
}
185+
return ret
186+
}
187+
188+
override fun toString(): String {
189+
return "MIME Type : $mimeType\t Width : $width\t Height : $height"
190+
}
159191
}

0 commit comments

Comments
 (0)