1429
|
1 import std/os
|
|
2 import std/typetraits
|
|
3 import std/strformat
|
|
4 import std/strutils
|
|
5 import std/streams
|
|
6
|
|
7 import ./core
|
|
8 import ./resources
|
|
9
|
|
10 {.emit: "#define STB_IMAGE_STATIC".}
|
|
11 {.emit: "#define STB_IMAGE_IMPLEMENTATION".}
|
|
12 {.
|
|
13 emit: "#include \"" & currentSourcePath.parentDir() & "/thirdparty/stb/stb_image.h\""
|
|
14 .}
|
|
15
|
|
16 proc stbi_load_from_memory(
|
|
17 buffer: ptr uint8,
|
|
18 len: cint,
|
|
19 x, y: ptr cint,
|
|
20 channels_in_file: ptr cint,
|
|
21 desired_channels: cint,
|
|
22 ): ptr uint8 {.importc, nodecl.}
|
|
23
|
|
24 func `$`*[S, IsArray](img: ImageObject[S, IsArray]): string =
|
|
25 let pixelTypeName = S.name
|
|
26 if IsArray == false:
|
|
27 $img.width & "x" & $img.height & " " & pixelTypeName
|
|
28 else:
|
|
29 $img.width & "x" & $img.height & "[" & $img.nLayers & "] " & pixelTypeName
|
|
30
|
|
31 func copy*[S, T](img: ImageObject[S, T]): ImageObject[S, T] =
|
|
32 for bf, rf in fields(img, result):
|
|
33 rf = bf
|
|
34
|
|
35 # loads single layer image
|
|
36 proc loadImageData*[T: PixelType](
|
|
37 pngData: string | seq[uint8]
|
|
38 ): tuple[width: uint32, height: uint32, data: seq[T]] =
|
|
39 when T is Gray:
|
|
40 let nChannels = 1.cint
|
|
41 elif T is BGRA:
|
|
42 let nChannels = 4.cint
|
|
43
|
|
44 var w, h, c: cint
|
|
45
|
|
46 let data = stbi_load_from_memory(
|
|
47 buffer = cast[ptr uint8](pngData.ToCPointer),
|
|
48 len = pngData.len.cint,
|
|
49 x = addr(w),
|
|
50 y = addr(h),
|
|
51 channels_in_file = addr(c),
|
|
52 desired_channels = nChannels,
|
|
53 )
|
|
54 if data == nil:
|
|
55 raise newException(Exception, "An error occured while loading PNG file")
|
|
56
|
|
57 let imagesize = w * h * nChannels
|
|
58 result = (width: w.uint32, height: h.uint32, data: newSeq[T](w * h))
|
|
59 copyMem(result.data.ToCPointer, data, imagesize)
|
|
60 nativeFree(data)
|
|
61
|
|
62 when T is BGRA: # convert to BGRA
|
|
63 for i in 0 ..< result.data.len:
|
|
64 swap(result.data[i][0], result.data[i][2])
|
|
65
|
|
66 # TODO: static versions to check for existing of files during compilation
|
|
67 proc addImage*[T: PixelType](imageArray: var ImageArray[T], image: sink Image[T]) =
|
|
68 assert image.width == imageArray.width,
|
|
69 &"Image needs to have same dimension as ImageArray to be added (array has {imageArray.width}x{imageArray.height} but image has {image.width}x{image.height})"
|
|
70 assert image.height == imageArray.height,
|
|
71 &"Image needs to have same dimension as ImageArray to be added (array has {imageArray.width}x{imageArray.height} but image has {image.width}x{image.height})"
|
|
72
|
|
73 inc imageArray.nLayers
|
|
74 imageArray.data.add image.data
|
|
75
|
|
76 proc `[]`*(image: Image, x, y: uint32): auto =
|
|
77 assert x < image.width, &"{x} < {image.width} is not true"
|
|
78 assert y < image.height, &"{y} < {image.height} is not true"
|
|
79
|
|
80 image.data[y * image.width + x]
|
|
81
|
|
82 proc `[]=`*[T](image: var Image[T], x, y: uint32, value: T) =
|
|
83 assert x < image.width
|
|
84 assert y < image.height
|
|
85
|
|
86 image.data[y * image.width + x] = value
|
|
87
|
|
88 proc `[]`*(image: ImageArray, layer, x, y: uint32): auto =
|
|
89 assert layer < image.nLayers,
|
|
90 &"Tried to access image layer {layer}, but image has only {image.nLayers} layers"
|
|
91 assert x < image.width,
|
|
92 &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}"
|
|
93 assert y < image.height,
|
|
94 &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}"
|
|
95
|
|
96 image.data[layer * (image.width * image.height) + y * image.width + x]
|
|
97
|
|
98 proc `[]=`*[T](image: var ImageArray[T], layer, x, y: uint32, value: T) =
|
|
99 assert layer < image.nLayers,
|
|
100 &"Tried to access image layer {layer}, but image has only {image.nLayers} layers"
|
|
101 assert x < image.width,
|
|
102 &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}"
|
|
103 assert y < image.height,
|
|
104 &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}"
|
|
105
|
|
106 image.data[layer * (image.width * image.height) + y * image.width + x] = value
|
|
107
|
|
108 # stb_image.h has no encoding support, maybe check stb_image_write or similar
|
|
109 #
|
|
110 # proc lodepng_encode_memory(out_data: ptr cstring, outsize: ptr csize_t, image: cstring, w: cuint, h: cuint, colorType: cint, bitdepth: cuint): cuint {.importc.}
|
|
111 #
|
|
112 #[
|
|
113 proc toPNG[T: PixelType](image: Image[T]): seq[uint8] =
|
|
114 when T is Gray:
|
|
115 let pngType = 0 # hardcoded in lodepng.h
|
|
116 else:
|
|
117 let pngType = 6 # hardcoded in lodepng.h
|
|
118 var
|
|
119 pngData: cstring
|
|
120 pngSize: csize_t
|
|
121 for y in 0 ..< image.height:
|
|
122 for x in 0 ..< image.width:
|
|
123 discard
|
|
124 let ret = lodepng_encode_memory(
|
|
125 addr pngData,
|
|
126 addr pngSize,
|
|
127 cast[cstring](image.imagedata.ToCPointer),
|
|
128 cuint(image.width),
|
|
129 cuint(image.height),
|
|
130 cint(pngType),
|
|
131 8,
|
|
132 )
|
|
133 assert ret == 0, &"There was an error with generating the PNG data for image {image}, result was: {ret}"
|
|
134 result = newSeq[uint8](pngSize)
|
|
135 for i in 0 ..< pngSize:
|
|
136 result[i] = uint8(pngData[i])
|
|
137 nativeFree(pngData)
|
|
138
|
|
139 proc WritePNG*(image: Image, filename: string) =
|
|
140 let f = filename.open(mode = fmWrite)
|
|
141 let data = image.toPNG()
|
|
142 let written = f.writeBytes(data, 0, data.len)
|
|
143 assert written == data.len, &"There was an error while saving '{filename}': only {written} of {data.len} bytes were written"
|
|
144 f.close()
|
|
145 ]#
|
|
146
|
|
147 proc loadImage*[T: PixelType](
|
|
148 path: string, package = DEFAULT_PACKAGE
|
|
149 ): Image[T] {.gcsafe.} =
|
|
150 assert path.splitFile().ext.toLowerAscii == ".png",
|
|
151 "Unsupported image type: " & path.splitFile().ext.toLowerAscii
|
|
152
|
|
153 let (width, height, data) =
|
|
154 loadImageData[T](loadResource_intern(path, package = package).readAll())
|
|
155 result = Image[T](width: width, height: height, data: data)
|
|
156
|
|
157 proc loadImageArray*[T: PixelType](
|
|
158 paths: openArray[string], package = DEFAULT_PACKAGE
|
|
159 ): ImageArray[T] {.gcsafe.} =
|
|
160 assert paths.len > 0, "Image array cannot contain 0 images"
|
|
161 for path in paths:
|
|
162 assert path.splitFile().ext.toLowerAscii == ".png",
|
|
163 "Unsupported image type: " & path.splitFile().ext.toLowerAscii
|
|
164
|
|
165 let (width, height, data) =
|
|
166 loadImageData[T](loadResource_intern(paths[0], package = package).readAll())
|
|
167 result =
|
|
168 ImageArray[T](width: width, height: height, data: data, nLayers: paths.len.uint32)
|
|
169 for path in paths[1 .. ^1]:
|
|
170 let (w, h, data) =
|
|
171 loadImageData[T](loadResource_intern(path, package = package).readAll())
|
|
172 assert w == result.width,
|
|
173 "New image layer has dimension {(w, y)} but image has dimension {(result.width, result.height)}"
|
|
174 assert h == result.height,
|
|
175 "New image layer has dimension {(w, y)} but image has dimension {(result.width, result.height)}"
|
|
176 result.data.add data
|
|
177
|
|
178 proc loadImageArray*[T: PixelType](
|
|
179 path: string, tilesize: uint32, package = DEFAULT_PACKAGE
|
|
180 ): ImageArray[T] {.gcsafe.} =
|
|
181 assert path.splitFile().ext.toLowerAscii == ".png",
|
|
182 "Unsupported image type: " & path.splitFile().ext.toLowerAscii
|
|
183
|
|
184 let (width, height, data) =
|
|
185 loadImageData[T](loadResource_intern(path, package = package).readAll())
|
|
186 let tilesY = height div tilesize
|
|
187
|
|
188 result = ImageArray[T](width: tilesize, height: tilesize)
|
|
189 var tile = newSeq[T](tilesize * tilesize)
|
|
190
|
|
191 for ty in 0 ..< tilesY:
|
|
192 for tx in 0 ..< tilesY:
|
|
193 var hasNonTransparent = when T is BGRA: false else: true
|
|
194 let baseI = ty * tilesize * width + tx * tilesize
|
|
195 for y in 0 ..< tilesize:
|
|
196 for x in 0 ..< tilesize:
|
|
197 tile[y * tilesize + x] = data[baseI + y * width + x]
|
|
198 when T is BGRA:
|
|
199 hasNonTransparent = hasNonTransparent or tile[y * tilesize + x].a > 0
|
|
200 if hasNonTransparent:
|
|
201 result.data.add tile
|
|
202 result.nLayers.inc
|