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
|
# Copyright 2018 The glTF-Blender-IO authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Imports
#
import struct
import zlib
class Image:
"""
Image object class to represent a 4-channel RGBA image.
Pixel values are expected to be floating point in the range of [0.0 to 1.0]
"""
def __init__(self, width, height, pixels):
self.width = width
self.height = height
self.channels = 4
self.pixels = pixels
self.name = ""
self.file_format = "PNG"
def to_png_data(self):
buf = bytearray([int(channel * 255.0) for channel in self.pixels])
#
# Taken from 'blender-thumbnailer.py' in Blender.
#
# reverse the vertical line order and add null bytes at the start
width_byte_4 = self.width * 4
raw_data = b"".join(
b'\x00' + buf[span:span + width_byte_4] for span in range(
(self.height - 1) * self.width * 4, -1, - width_byte_4))
def png_pack(png_tag, data):
chunk_head = png_tag + data
return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))
return b"".join([
b'\x89PNG\r\n\x1a\n',
png_pack(b'IHDR', struct.pack("!2I5B", self.width, self.height, 8, 6, 0, 0, 0)),
png_pack(b'IDAT', zlib.compress(raw_data, 9)),
png_pack(b'IEND', b'')])
def to_image_data(self, mime_type):
if mime_type == 'image/png':
return self.to_png_data()
raise ValueError("Unsupported image file type {}".format(mime_type))
def save_png(self, dst_path):
data = self.to_png_data()
with open(dst_path, 'wb') as f:
f.write(data)
def create_img(width, height, r=0.0, g=0.0, b=0.0, a=1.0):
"""
Create a new image object with 4 channels and initialize it with the given default values.
(if no arguments are given, these default to R=0, G=0, B=0, A=1.0)
Return the created image object.
"""
return Image(width, height, [r, g, b, a] * (width * height))
def create_img_from_pixels(width, height, pixels):
"""
Create a new image object with 4 channels and initialize it using the given array of pixel data.
Return the created image object.
"""
if pixels is None or len(pixels) != width * height * 4:
return None
return Image(width, height, pixels)
def copy_img_channel(dst_image, dst_channel, src_image, src_channel):
"""
Copy a single channel (identified by src_channel) from src_image to dst_image (overwriting dst_channel).
src_image and dst_image are expected to be image objects created using create_img.
Return True on success, False otherwise.
"""
if dst_image is None or src_image is None:
return False
if dst_channel < 0 or dst_channel >= dst_image.channels or src_channel < 0 or src_channel >= src_image.channels:
return False
if src_image.width != dst_image.width or \
src_image.height != dst_image.height or \
src_image.channels != dst_image.channels:
return False
for i in range(0, len(dst_image.pixels), dst_image.channels):
dst_image.pixels[i + dst_channel] = src_image.pixels[i + src_channel]
return True
def test_save_img(image, path):
"""
Save the given image to a PNG file (specified by path).
Return True on success, False otherwise.
"""
if image is None or image.channels != 4:
return False
width = image.width
height = image.height
buf = bytearray([int(channel * 255.0) for channel in image.pixels])
#
# Taken from 'blender-thumbnailer.py' in Blender.
#
# reverse the vertical line order and add null bytes at the start
width_byte_4 = width * 4
raw_data = b"".join(
b'\x00' + buf[span:span + width_byte_4] for span in range((height - 1) * width * 4, -1, - width_byte_4))
def png_pack(png_tag, data):
chunk_head = png_tag + data
return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))
data = b"".join([
b'\x89PNG\r\n\x1a\n',
png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)),
png_pack(b'IDAT', zlib.compress(raw_data, 9)),
png_pack(b'IEND', b'')])
with open(path, 'wb') as f:
f.write(data)
return True
|