998 lines
21 KiB
Go
998 lines
21 KiB
Go
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"sync"
|
|
"unsafe"
|
|
)
|
|
|
|
func CVAL(p *[]uint8) int {
|
|
a := int((*p)[0])
|
|
*p = (*p)[1:]
|
|
return a
|
|
}
|
|
|
|
func CVAL2(p *[]uint8, v *uint16) {
|
|
*v = *((*uint16)(unsafe.Pointer(&(*p)[0])))
|
|
*p = (*p)[2:]
|
|
}
|
|
|
|
func CVAL3(p *[]uint8, v *[3]uint8) {
|
|
(*v)[0] = (*p)[0]
|
|
(*v)[1] = (*p)[1]
|
|
(*v)[2] = (*p)[2]
|
|
*p = (*p)[3:]
|
|
}
|
|
|
|
func REPEAT(f func(), count *int, x *int, width int) {
|
|
for *count > 0 && *x < width {
|
|
f()
|
|
*count--
|
|
*x++
|
|
}
|
|
}
|
|
|
|
// rleFailLog 记录 RLE 解码失败(限次),此前失败被静默吞掉:
|
|
// 半解码的池化缓冲被直接画上画布形成噪块,且无任何日志可查。
|
|
func rleFailLog(reason string, width, height, inputLen int, consumed int) {
|
|
slog.Warn("bitmap RLE decode failed", "reason", reason,
|
|
"w", width, "h", height, "inputLen", inputLen, "consumed", consumed)
|
|
}
|
|
|
|
/* 1 byte bitmap decompress */
|
|
func decompress1(output *[]uint8, width, height int, input []uint8, size int) bool {
|
|
var (
|
|
prevline, line, count int
|
|
offset, code int
|
|
x int = width
|
|
opcode int
|
|
lastopcode int8 = -1
|
|
insertmix, bicolour, isfillormix bool
|
|
mixmask, mask uint8
|
|
colour1, colour2 uint8
|
|
mix uint8 = 0xff
|
|
fom_mask uint8
|
|
)
|
|
out := *output
|
|
for len(input) != 0 {
|
|
fom_mask = 0
|
|
code = CVAL(&input)
|
|
opcode = code >> 4
|
|
/* Handle different opcode forms */
|
|
switch opcode {
|
|
case 0xc, 0xd, 0xe:
|
|
opcode -= 6
|
|
count = int(code & 0xf)
|
|
offset = 16
|
|
break
|
|
case 0xf:
|
|
opcode = code & 0xf
|
|
if opcode < 9 {
|
|
count = int(CVAL(&input))
|
|
count |= int(CVAL(&input) << 8)
|
|
} else {
|
|
count = 1
|
|
if opcode < 0xb {
|
|
count = 8
|
|
}
|
|
}
|
|
offset = 0
|
|
break
|
|
default:
|
|
opcode >>= 1
|
|
count = int(code & 0x1f)
|
|
offset = 32
|
|
break
|
|
}
|
|
/* Handle strange cases for counts */
|
|
if offset != 0 {
|
|
isfillormix = ((opcode == 2) || (opcode == 7))
|
|
if count == 0 {
|
|
if isfillormix {
|
|
count = int(CVAL(&input)) + 1
|
|
} else {
|
|
count = int(CVAL(&input) + offset)
|
|
}
|
|
} else if isfillormix {
|
|
count <<= 3
|
|
}
|
|
}
|
|
/* Read preliminary data */
|
|
switch opcode {
|
|
case 0: /* Fill */
|
|
if (lastopcode == int8(opcode)) && !((x == width) && (prevline == 0)) {
|
|
insertmix = true
|
|
}
|
|
break
|
|
case 8: /* Bicolour */
|
|
colour1 = uint8(CVAL(&input))
|
|
colour2 = uint8(CVAL(&input))
|
|
break
|
|
case 3: /* Colour */
|
|
colour2 = uint8(CVAL(&input))
|
|
break
|
|
case 6: /* SetMix/Mix */
|
|
fallthrough
|
|
case 7: /* SetMix/FillOrMix */
|
|
mix = uint8(CVAL(&input))
|
|
opcode -= 5
|
|
break
|
|
case 9: /* FillOrMix_1 */
|
|
mask = 0x03
|
|
opcode = 0x02
|
|
fom_mask = 3
|
|
break
|
|
case 0x0a: /* FillOrMix_2 */
|
|
mask = 0x05
|
|
opcode = 0x02
|
|
fom_mask = 5
|
|
break
|
|
}
|
|
lastopcode = int8(opcode)
|
|
mixmask = 0
|
|
/* Output body */
|
|
for count > 0 {
|
|
if x >= width {
|
|
if height <= 0 {
|
|
return false
|
|
}
|
|
|
|
x = 0
|
|
height--
|
|
prevline = line
|
|
line = height * width
|
|
}
|
|
switch opcode {
|
|
case 0: /* Fill */
|
|
if insertmix {
|
|
if prevline == 0 {
|
|
out[x+line] = mix
|
|
} else {
|
|
out[x+line] = out[prevline+x] ^ mix
|
|
}
|
|
insertmix = false
|
|
count--
|
|
x++
|
|
}
|
|
n := min(count, width-x)
|
|
if prevline == 0 {
|
|
clear(out[x+line : x+line+n])
|
|
} else {
|
|
copy(out[x+line:x+line+n], out[prevline+x:prevline+x+n])
|
|
}
|
|
count -= n
|
|
x += n
|
|
break
|
|
case 1: /* Mix */
|
|
n := min(count, width-x)
|
|
if prevline == 0 {
|
|
seg := out[x+line : x+line+n]
|
|
for i := range seg {
|
|
seg[i] = mix
|
|
}
|
|
} else {
|
|
src := out[prevline+x : prevline+x+n]
|
|
dst := out[x+line : x+line+n]
|
|
for i := range dst {
|
|
dst[i] = src[i] ^ mix
|
|
}
|
|
}
|
|
count -= n
|
|
x += n
|
|
break
|
|
case 2: /* Fill or Mix */
|
|
if prevline == 0 {
|
|
for count > 0 && x < width {
|
|
mixmask <<= 1
|
|
if mixmask == 0 {
|
|
mask = fom_mask
|
|
if fom_mask == 0 {
|
|
mask = uint8(CVAL(&input))
|
|
mixmask = 1
|
|
}
|
|
}
|
|
if mask&mixmask != 0 {
|
|
out[x+line] = mix
|
|
} else {
|
|
out[x+line] = 0
|
|
}
|
|
|
|
count--
|
|
x++
|
|
}
|
|
} else {
|
|
for count > 0 && x < width {
|
|
mixmask = mixmask << 1
|
|
if mixmask == 0 {
|
|
mask = fom_mask
|
|
if fom_mask == 0 {
|
|
mask = uint8(CVAL(&input))
|
|
mixmask = 1
|
|
}
|
|
}
|
|
if mask&mixmask != 0 {
|
|
out[x+line] = out[prevline+x] ^ mix
|
|
} else {
|
|
out[x+line] = out[prevline+x]
|
|
}
|
|
|
|
count--
|
|
x++
|
|
}
|
|
}
|
|
break
|
|
case 3: /* Colour */
|
|
n := min(count, width-x)
|
|
seg := out[x+line : x+line+n]
|
|
for i := range seg {
|
|
seg[i] = colour2
|
|
}
|
|
count -= n
|
|
x += n
|
|
break
|
|
case 4: /* Copy */
|
|
for count > 0 && x < width {
|
|
n := min(count, width-x)
|
|
if len(input) < n {
|
|
return false
|
|
}
|
|
copy(out[x+line:x+line+n], input[:n])
|
|
input = input[n:]
|
|
count -= n
|
|
x += n
|
|
}
|
|
break
|
|
case 8: /* Bicolour */
|
|
for count > 0 && x < width {
|
|
if bicolour {
|
|
out[x+line] = colour2
|
|
bicolour = false
|
|
} else {
|
|
out[x+line] = colour1
|
|
bicolour = true
|
|
count++
|
|
}
|
|
|
|
count--
|
|
x++
|
|
}
|
|
|
|
break
|
|
|
|
case 0xd: /* White */
|
|
n := min(count, width-x)
|
|
seg := out[x+line : x+line+n]
|
|
for i := range seg {
|
|
seg[i] = 0xff
|
|
}
|
|
count -= n
|
|
x += n
|
|
break
|
|
case 0xe: /* Black */
|
|
n := min(count, width-x)
|
|
clear(out[x+line : x+line+n])
|
|
count -= n
|
|
x += n
|
|
break
|
|
default:
|
|
fmt.Printf("bitmap opcode 0x%x\n", opcode)
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// decompress2Pool reuses the intermediate []uint16 buffer across calls to
|
|
// decompress2, avoiding a large per-frame allocation.
|
|
var decompress2Pool sync.Pool
|
|
|
|
/* 2 byte bitmap decompress */
|
|
func decompress2(output *[]uint8, width, height int, input []uint8, size int) bool {
|
|
needed := width * height
|
|
|
|
var out []uint16
|
|
if v := decompress2Pool.Get(); v != nil {
|
|
out = v.([]uint16)
|
|
if cap(out) < needed {
|
|
out = make([]uint16, needed)
|
|
} else {
|
|
out = out[:needed]
|
|
}
|
|
} else {
|
|
out = make([]uint16, needed)
|
|
}
|
|
defer func() { decompress2Pool.Put(out[:cap(out)]) }()
|
|
|
|
var (
|
|
prevline, line, count int
|
|
offset, code int
|
|
x int = width
|
|
opcode int
|
|
lastopcode int = -1
|
|
insertmix, bicolour, isfillormix bool
|
|
mixmask, mask uint8
|
|
colour1, colour2 uint16
|
|
mix uint16 = 0xffff
|
|
fom_mask uint8
|
|
)
|
|
inputLen0 := len(input)
|
|
|
|
for len(input) != 0 {
|
|
fom_mask = 0
|
|
code = CVAL(&input)
|
|
opcode = code >> 4
|
|
/* Handle different opcode forms */
|
|
switch opcode {
|
|
case 0xc, 0xd, 0xe:
|
|
opcode -= 6
|
|
count = code & 0xf
|
|
offset = 16
|
|
break
|
|
case 0xf:
|
|
opcode = code & 0xf
|
|
if opcode < 9 {
|
|
count = CVAL(&input)
|
|
count |= CVAL(&input) << 8
|
|
} else {
|
|
count = 1
|
|
if opcode < 0xb {
|
|
count = 8
|
|
}
|
|
}
|
|
offset = 0
|
|
break
|
|
default:
|
|
opcode >>= 1
|
|
count = code & 0x1f
|
|
offset = 32
|
|
break
|
|
}
|
|
|
|
/* Handle strange cases for counts */
|
|
if offset != 0 {
|
|
isfillormix = ((opcode == 2) || (opcode == 7))
|
|
if count == 0 {
|
|
if isfillormix {
|
|
count = CVAL(&input) + 1
|
|
} else {
|
|
count = CVAL(&input) + offset
|
|
}
|
|
} else if isfillormix {
|
|
count <<= 3
|
|
}
|
|
}
|
|
/* Read preliminary data */
|
|
switch opcode {
|
|
case 0: /* Fill */
|
|
if (lastopcode == opcode) && !((x == width) && (prevline == 0)) {
|
|
insertmix = true
|
|
}
|
|
break
|
|
case 8: /* Bicolour */
|
|
CVAL2(&input, &colour1)
|
|
CVAL2(&input, &colour2)
|
|
break
|
|
case 3: /* Colour */
|
|
CVAL2(&input, &colour2)
|
|
break
|
|
case 6: /* SetMix/Mix */
|
|
fallthrough
|
|
case 7: /* SetMix/FillOrMix */
|
|
CVAL2(&input, &mix)
|
|
opcode -= 5
|
|
break
|
|
case 9: /* FillOrMix_1 */
|
|
mask = 0x03
|
|
opcode = 0x02
|
|
fom_mask = 3
|
|
break
|
|
case 0x0a: /* FillOrMix_2 */
|
|
mask = 0x05
|
|
opcode = 0x02
|
|
fom_mask = 5
|
|
break
|
|
}
|
|
lastopcode = opcode
|
|
mixmask = 0
|
|
/* Output body */
|
|
for count > 0 {
|
|
if x >= width {
|
|
if height <= 0 {
|
|
rleFailLog("lines exhausted", width, height, inputLen0, inputLen0-len(input))
|
|
return false
|
|
}
|
|
|
|
x = 0
|
|
height--
|
|
prevline = line
|
|
line = height * width
|
|
}
|
|
switch opcode {
|
|
case 0: /* Fill */
|
|
if insertmix {
|
|
if prevline == 0 {
|
|
out[x+line] = mix
|
|
} else {
|
|
out[x+line] = out[prevline+x] ^ mix
|
|
}
|
|
insertmix = false
|
|
count--
|
|
x++
|
|
}
|
|
n := min(count, width-x)
|
|
if prevline == 0 {
|
|
clear(out[x+line : x+line+n])
|
|
} else {
|
|
copy(out[x+line:x+line+n], out[prevline+x:prevline+x+n])
|
|
}
|
|
count -= n
|
|
x += n
|
|
break
|
|
case 1: /* Mix */
|
|
n := min(count, width-x)
|
|
if prevline == 0 {
|
|
seg := out[x+line : x+line+n]
|
|
for i := range seg {
|
|
seg[i] = mix
|
|
}
|
|
} else {
|
|
src := out[prevline+x : prevline+x+n]
|
|
dst := out[x+line : x+line+n]
|
|
for i := range dst {
|
|
dst[i] = src[i] ^ mix
|
|
}
|
|
}
|
|
count -= n
|
|
x += n
|
|
break
|
|
case 2: /* Fill or Mix */
|
|
if prevline == 0 {
|
|
for count > 0 && x < width {
|
|
mixmask <<= 1
|
|
if mixmask == 0 {
|
|
mask = fom_mask
|
|
if fom_mask == 0 {
|
|
mask = uint8(CVAL(&input))
|
|
mixmask = 1
|
|
}
|
|
}
|
|
if mask&mixmask != 0 {
|
|
out[x+line] = mix
|
|
} else {
|
|
out[x+line] = 0
|
|
}
|
|
|
|
count--
|
|
x++
|
|
}
|
|
} else {
|
|
for count > 0 && x < width {
|
|
mixmask = mixmask << 1
|
|
if mixmask == 0 {
|
|
mask = fom_mask
|
|
if fom_mask == 0 {
|
|
mask = uint8(CVAL(&input))
|
|
mixmask = 1
|
|
}
|
|
}
|
|
if mask&mixmask != 0 {
|
|
out[x+line] = out[prevline+x] ^ mix
|
|
} else {
|
|
out[x+line] = out[prevline+x]
|
|
}
|
|
|
|
count--
|
|
x++
|
|
}
|
|
}
|
|
break
|
|
case 3: /* Colour */
|
|
n := min(count, width-x)
|
|
seg := out[x+line : x+line+n]
|
|
for i := range seg {
|
|
seg[i] = colour2
|
|
}
|
|
count -= n
|
|
x += n
|
|
break
|
|
case 4: /* Copy */
|
|
for count > 0 && x < width {
|
|
n := min(count, width-x)
|
|
if len(input) < n*2 {
|
|
rleFailLog("copy input exhausted", width, height, inputLen0, inputLen0-len(input))
|
|
return false
|
|
}
|
|
copy(out[x+line:x+line+n], unsafe.Slice((*uint16)(unsafe.Pointer(&input[0])), n))
|
|
input = input[n*2:]
|
|
count -= n
|
|
x += n
|
|
}
|
|
|
|
break
|
|
case 8: /* Bicolour */
|
|
for count > 0 && x < width {
|
|
if bicolour {
|
|
out[x+line] = colour2
|
|
bicolour = false
|
|
} else {
|
|
out[x+line] = colour1
|
|
bicolour = true
|
|
count++
|
|
}
|
|
|
|
count--
|
|
x++
|
|
}
|
|
|
|
break
|
|
case 0xd: /* White */
|
|
n2 := min(count, width-x)
|
|
seg2 := out[x+line : x+line+n2]
|
|
for i := range seg2 {
|
|
seg2[i] = 0xffff
|
|
}
|
|
count -= n2
|
|
x += n2
|
|
break
|
|
case 0xe: /* Black */
|
|
n3 := min(count, width-x)
|
|
clear(out[x+line : x+line+n3])
|
|
count -= n3
|
|
x += n3
|
|
break
|
|
default:
|
|
rleFailLog(fmt.Sprintf("bad opcode 0x%x", opcode), width, height, inputLen0, inputLen0-len(input))
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
outBytes := *output
|
|
for i, v := range out {
|
|
outBytes[i*2] = byte(v >> 8)
|
|
outBytes[i*2+1] = byte(v)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// /* 3 byte bitmap decompress */
|
|
func decompress3(output *[]uint8, width, height int, input []uint8, size int) bool {
|
|
var (
|
|
prevline, line, count int
|
|
opcode, offset, code int
|
|
x int = width
|
|
lastopcode int = -1
|
|
insertmix, bicolour, isfillormix bool
|
|
mixmask, mask uint8
|
|
colour1 = [3]uint8{0, 0, 0}
|
|
colour2 = [3]uint8{0, 0, 0}
|
|
mix = [3]uint8{0xff, 0xff, 0xff}
|
|
fom_mask uint8
|
|
)
|
|
out := *output
|
|
for len(input) != 0 {
|
|
fom_mask = 0
|
|
code = CVAL(&input)
|
|
opcode = code >> 4
|
|
/* Handle different opcode forms */
|
|
switch opcode {
|
|
case 0xc, 0xd, 0xe:
|
|
opcode -= 6
|
|
count = code & 0xf
|
|
offset = 16
|
|
break
|
|
case 0xf:
|
|
opcode = code & 0xf
|
|
if opcode < 9 {
|
|
count = CVAL(&input)
|
|
count |= CVAL(&input) << 8
|
|
} else {
|
|
count = 1
|
|
if opcode < 0xb {
|
|
count = 8
|
|
}
|
|
}
|
|
offset = 0
|
|
break
|
|
default:
|
|
opcode >>= 1
|
|
count = code & 0x1f
|
|
offset = 32
|
|
break
|
|
}
|
|
|
|
/* Handle strange cases for counts */
|
|
if offset != 0 {
|
|
isfillormix = ((opcode == 2) || (opcode == 7))
|
|
if count == 0 {
|
|
if isfillormix {
|
|
count = CVAL(&input) + 1
|
|
} else {
|
|
count = CVAL(&input) + offset
|
|
}
|
|
} else if isfillormix {
|
|
count <<= 3
|
|
}
|
|
}
|
|
/* Read preliminary data */
|
|
switch opcode {
|
|
case 0: /* Fill */
|
|
if (lastopcode == opcode) && !((x == width) && (prevline == 0)) {
|
|
insertmix = true
|
|
}
|
|
break
|
|
case 8: /* Bicolour */
|
|
CVAL3(&input, &colour1)
|
|
CVAL3(&input, &colour2)
|
|
break
|
|
case 3: /* Colour */
|
|
CVAL3(&input, &colour2)
|
|
break
|
|
case 6: /* SetMix/Mix */
|
|
fallthrough
|
|
case 7: /* SetMix/FillOrMix */
|
|
CVAL3(&input, &mix)
|
|
opcode -= 5
|
|
break
|
|
case 9: /* FillOrMix_1 */
|
|
mask = 0x03
|
|
opcode = 0x02
|
|
fom_mask = 3
|
|
break
|
|
case 0x0a: /* FillOrMix_2 */
|
|
mask = 0x05
|
|
opcode = 0x02
|
|
fom_mask = 5
|
|
break
|
|
}
|
|
|
|
lastopcode = opcode
|
|
mixmask = 0
|
|
/* Output body */
|
|
for count > 0 {
|
|
if x >= width {
|
|
if height <= 0 {
|
|
return false
|
|
}
|
|
|
|
x = 0
|
|
height--
|
|
prevline = line
|
|
line = height * width * 3
|
|
}
|
|
switch opcode {
|
|
case 0: /* Fill */
|
|
if insertmix {
|
|
if prevline == 0 {
|
|
out[3*x+line] = mix[0]
|
|
out[3*x+line+1] = mix[1]
|
|
out[3*x+line+2] = mix[2]
|
|
} else {
|
|
out[3*x+line] = out[prevline+3*x] ^ mix[0]
|
|
out[3*x+line+1] = out[prevline+3*x+1] ^ mix[1]
|
|
out[3*x+line+2] = out[prevline+3*x+2] ^ mix[2]
|
|
}
|
|
insertmix = false
|
|
count--
|
|
x++
|
|
}
|
|
n := min(count, width-x)
|
|
if prevline == 0 {
|
|
clear(out[3*x+line : 3*x+line+3*n])
|
|
} else {
|
|
dstBase := 3*x + line
|
|
srcBase := prevline + 3*x
|
|
copy(out[dstBase:dstBase+3*n], out[srcBase:srcBase+3*n])
|
|
}
|
|
count -= n
|
|
x += n
|
|
break
|
|
case 1: /* Mix */
|
|
n := min(count, width-x)
|
|
dst1 := out[3*x+line : 3*x+line+3*n]
|
|
if prevline == 0 {
|
|
// Exponential-doubling copy: O(log n) memcpy calls
|
|
dst1[0], dst1[1], dst1[2] = mix[0], mix[1], mix[2]
|
|
for wrote := 3; wrote < len(dst1); {
|
|
wrote += copy(dst1[wrote:], dst1[:wrote])
|
|
}
|
|
} else {
|
|
src1 := out[prevline+3*x : prevline+3*x+3*n]
|
|
for i := 0; i+2 < len(dst1); i += 3 {
|
|
dst1[i] = src1[i] ^ mix[0]
|
|
dst1[i+1] = src1[i+1] ^ mix[1]
|
|
dst1[i+2] = src1[i+2] ^ mix[2]
|
|
}
|
|
}
|
|
count -= n
|
|
x += n
|
|
break
|
|
case 2: /* Fill or Mix */
|
|
if prevline == 0 {
|
|
base := 3*x + line
|
|
for count > 0 && x < width {
|
|
mixmask = mixmask << 1
|
|
if mixmask == 0 {
|
|
mask = fom_mask
|
|
if fom_mask == 0 {
|
|
mask = uint8(CVAL(&input))
|
|
mixmask = 1
|
|
}
|
|
}
|
|
if mask&mixmask != 0 {
|
|
out[base] = mix[0]
|
|
out[base+1] = mix[1]
|
|
out[base+2] = mix[2]
|
|
} else {
|
|
out[base] = 0
|
|
out[base+1] = 0
|
|
out[base+2] = 0
|
|
}
|
|
base += 3
|
|
count--
|
|
x++
|
|
}
|
|
} else {
|
|
base := 3*x + line
|
|
prev := prevline + 3*x
|
|
for count > 0 && x < width {
|
|
mixmask = mixmask << 1
|
|
if mixmask == 0 {
|
|
mask = fom_mask
|
|
if fom_mask == 0 {
|
|
mask = uint8(CVAL(&input))
|
|
mixmask = 1
|
|
}
|
|
}
|
|
if mask&mixmask != 0 {
|
|
out[base] = out[prev] ^ mix[0]
|
|
out[base+1] = out[prev+1] ^ mix[1]
|
|
out[base+2] = out[prev+2] ^ mix[2]
|
|
} else {
|
|
out[base] = out[prev]
|
|
out[base+1] = out[prev+1]
|
|
out[base+2] = out[prev+2]
|
|
}
|
|
base += 3
|
|
prev += 3
|
|
count--
|
|
x++
|
|
}
|
|
}
|
|
break
|
|
case 3: /* Colour */
|
|
n := min(count, width-x)
|
|
seg3 := out[3*x+line : 3*x+line+3*n]
|
|
seg3[0], seg3[1], seg3[2] = colour2[0], colour2[1], colour2[2]
|
|
for wrote := 3; wrote < len(seg3); {
|
|
wrote += copy(seg3[wrote:], seg3[:wrote])
|
|
}
|
|
count -= n
|
|
x += n
|
|
break
|
|
case 4: /* Copy */
|
|
for count > 0 && x < width {
|
|
n := min(count, width-x)
|
|
if len(input) < n*3 {
|
|
return false
|
|
}
|
|
copy(out[3*x+line:3*x+line+n*3], input[:n*3])
|
|
input = input[n*3:]
|
|
count -= n
|
|
x += n
|
|
}
|
|
break
|
|
case 8: /* Bicolour */
|
|
base8 := 3*x + line
|
|
for count > 0 && x < width {
|
|
if bicolour {
|
|
out[base8] = colour2[0]
|
|
out[base8+1] = colour2[1]
|
|
out[base8+2] = colour2[2]
|
|
bicolour = false
|
|
} else {
|
|
out[base8] = colour1[0]
|
|
out[base8+1] = colour1[1]
|
|
out[base8+2] = colour1[2]
|
|
bicolour = true
|
|
count++
|
|
}
|
|
base8 += 3
|
|
count--
|
|
x++
|
|
}
|
|
break
|
|
case 0xd: /* White */
|
|
n3 := min(count, width-x)
|
|
seg3 := out[3*x+line : 3*x+line+3*n3]
|
|
for i := range seg3 {
|
|
seg3[i] = 0xff
|
|
}
|
|
count -= n3
|
|
x += n3
|
|
break
|
|
case 0xe: /* Black */
|
|
n2 := min(count, width-x)
|
|
clear(out[3*x+line : 3*x+line+3*n2])
|
|
count -= n2
|
|
x += n2
|
|
break
|
|
default:
|
|
fmt.Printf("bitmap opcode 0x%x\n", opcode)
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
/* decompress a colour plane */
|
|
func processPlane(in *[]uint8, width, height int, output *[]uint8, j int) int {
|
|
var (
|
|
indexw int
|
|
indexh int
|
|
code int
|
|
collen int
|
|
replen int
|
|
color uint8
|
|
x uint8
|
|
revcode int
|
|
lastline int
|
|
thisline int
|
|
)
|
|
ln := len(*in)
|
|
out := *output // hoist pointer dereference; writes to out[i] affect the underlying array
|
|
|
|
lastline = 0
|
|
indexh = 0
|
|
i := 0
|
|
for indexh < height {
|
|
thisline = j + (width * height * 4) - ((indexh + 1) * width * 4)
|
|
color = 0
|
|
indexw = 0
|
|
i = thisline
|
|
|
|
if lastline == 0 {
|
|
for indexw < width {
|
|
code = CVAL(in)
|
|
replen = int(code & 0xf)
|
|
collen = int((code >> 4) & 0xf)
|
|
revcode = (replen << 4) | collen
|
|
if (revcode <= 47) && (revcode >= 16) {
|
|
replen = revcode
|
|
collen = 0
|
|
}
|
|
for collen > 0 {
|
|
color = uint8(CVAL(in))
|
|
out[i] = color
|
|
i += 4
|
|
|
|
indexw++
|
|
collen--
|
|
}
|
|
for replen > 0 {
|
|
out[i] = color
|
|
i += 4
|
|
indexw++
|
|
replen--
|
|
}
|
|
}
|
|
} else {
|
|
// prevOffset is constant per row: indexw*4+lastline == i+(lastline-thisline)
|
|
// because i == thisline+indexw*4. Pre-computing it eliminates a multiply
|
|
// per pixel in both inner loops.
|
|
prevOffset := lastline - thisline
|
|
for indexw < width {
|
|
code = CVAL(in)
|
|
replen = int(code & 0xf)
|
|
collen = int((code >> 4) & 0xf)
|
|
revcode = (replen << 4) | collen
|
|
if (revcode <= 47) && (revcode >= 16) {
|
|
replen = revcode
|
|
collen = 0
|
|
}
|
|
for collen > 0 {
|
|
x = uint8(CVAL(in))
|
|
if x&1 != 0 {
|
|
x = x >> 1
|
|
x = x + 1
|
|
color = -x
|
|
} else {
|
|
x = x >> 1
|
|
color = x
|
|
}
|
|
x = out[i+prevOffset] + color
|
|
out[i] = x
|
|
i += 4
|
|
indexw++
|
|
collen--
|
|
}
|
|
for replen > 0 {
|
|
x = out[i+prevOffset] + color
|
|
out[i] = x
|
|
i += 4
|
|
indexw++
|
|
replen--
|
|
}
|
|
}
|
|
}
|
|
indexh++
|
|
lastline = thisline
|
|
}
|
|
return ln - len(*in)
|
|
}
|
|
|
|
/* 4 byte bitmap decompress */
|
|
func decompress4(output *[]uint8, width, height int, input []uint8, size int) bool {
|
|
var (
|
|
code int
|
|
onceBytes, total int
|
|
)
|
|
|
|
code = CVAL(&input)
|
|
rle := code&0x10 != 0
|
|
noAlpha := code&0x20 != 0
|
|
|
|
if !rle {
|
|
return false
|
|
}
|
|
|
|
total = 1
|
|
out := *output
|
|
|
|
if noAlpha {
|
|
// No alpha plane in the stream; fill alpha channel with 0xFF.
|
|
for i := 3; i < len(out); i += 4 {
|
|
out[i] = 0xFF
|
|
}
|
|
} else {
|
|
onceBytes = processPlane(&input, width, height, output, 3)
|
|
total += onceBytes
|
|
}
|
|
|
|
onceBytes = processPlane(&input, width, height, output, 2)
|
|
total += onceBytes
|
|
|
|
onceBytes = processPlane(&input, width, height, output, 1)
|
|
total += onceBytes
|
|
|
|
onceBytes = processPlane(&input, width, height, output, 0)
|
|
total += onceBytes
|
|
|
|
return true
|
|
}
|
|
|
|
// DecompressInto decompresses bitmap data into dst, reusing dst if it has
|
|
// sufficient capacity (size = width*height*bpp). If dst is nil or too small
|
|
// a new slice is allocated. Returns the (re)used output slice.
|
|
func DecompressInto(input []uint8, dst []uint8, width, height int, bpp int) ([]uint8, bool) {
|
|
size := width * height * bpp
|
|
if cap(dst) >= size {
|
|
dst = dst[:size]
|
|
} else {
|
|
dst = make([]uint8, size)
|
|
}
|
|
ok := false
|
|
switch bpp {
|
|
case 1:
|
|
ok = decompress1(&dst, width, height, input, size)
|
|
case 2:
|
|
ok = decompress2(&dst, width, height, input, size)
|
|
case 3:
|
|
ok = decompress3(&dst, width, height, input, size)
|
|
case 4:
|
|
ok = decompress4(&dst, width, height, input, size)
|
|
default:
|
|
fmt.Printf("bpp %d\n", bpp)
|
|
}
|
|
return dst, ok
|
|
}
|
|
|
|
/* main decompress function */
|
|
func Decompress(input []uint8, width, height int, bpp int) []uint8 {
|
|
out, _ := DecompressInto(input, nil, width, height, bpp)
|
|
return out
|
|
}
|