*: remove newly superfluous static types on attributes
[nit.git] / lib / bitmap / bitmap.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2015 Budi Kurniawan <budi2020@gmail.com>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # The Bitmap class represents a 24-bit bitmap image. An instance can be constructed
18 # by calling load or with_size:
19 #
20 # ~~~nitish
21 # var bm1 = new Bitmap.load(path_to_a_bmp_file)
22 # var bm2 = new Bitmap.with_size(400, 300)
23 # ~~~
24 #
25 # The width and height attributes contain the image's width and height,
26 # respectively.
27 #
28 # Individual pixels can be manipulated by calling the set_pixel function:
29 #
30 # ~~~nitish
31 # set_pixel(x, y, color)
32 # ~~~
33 #
34 # The no-argument grayscale function converts the bitmap to grayscale and is an
35 # implementation of this Rossetacode task:
36 # http://rosettacode.org/wiki/Grayscale_image
37 #
38 # Finally, the bitmap can be saved to a file by calling save:
39 #
40 # ~~~nitish
41 # save(path_to_a_file)
42 # ~~~
43 #
44 # For information on the bitmap format, see
45 # http://en.wikipedia.org/wiki/BMP_file_format
46 #
47 # A module containing classes for manipulating bitmap images
48 module bitmap
49 #
50 # A class that represents a Bitmap image
51 class Bitmap
52 # The file path if this Bitmap is created by loading a bitmap file
53 var file_path: String is noinit
54
55 # The file extension if this Bitmap is created by loading a bitmap file
56 var file_extension: String is noinit
57
58 # The file size if this Bitmap is created by loading a bitmap file
59 var file_size: Int is noinit
60
61 # The image width in pixels
62 var width: Int is noinit
63
64 # The image height in pixels
65 var height: Int is noinit
66
67 # The offset of image data. Typically, 54
68 private var data_offset: Int is noinit
69
70 # The number of bits representing a pixel. Currently only 24-bit bitmaps are supported
71 private var bits_per_pixel: Int is noinit
72
73 # The size of images. For a 24-bit bitmap, this is equal to (3*width*height)
74 private var image_size: Int is noinit
75
76 # 14-byte bitmap header
77 private var bitmap_header = [66, 77, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0]
78
79 # 40-byte dib header
80 private var dib_header = [40, 0, 0, 0, 0, 0, 0, 0, 0, 0,
81 0, 0, 1, 0, 24, 0, 0, 0, 0, 0,
82 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
83 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
84
85 # The image data, each cell contains a pixel.
86 # The first 8 least-significant bits represent the red component,
87 # the next 8 least-significant bits represent the green component,
88 # and the next 8 bit represent the blue component.
89 # The last 8 most-significant bits are not used
90 private var data = new Array[Array[Int]]
91
92 # Constructs a Bitmap object by passing the image's width and height (in pixels)
93 init with_size(width: Int, height: Int)
94 do
95 self.width = width
96 self.height = height
97 self.bits_per_pixel = 24
98 self.data_offset = 54
99 #set width and height
100
101 self.set_value(dib_header, 4, width)
102 self.set_value(dib_header, 8, height)
103 #set file size
104 var file_size = 3 * width * height + 54
105 self.set_value(bitmap_header, 2, file_size)
106
107 #init pixel data
108 self.data = new Array[Array[Int]].with_capacity(height)
109 for x in [0..height[ do
110 var row = new Array[Int].with_capacity(width)
111 for y in [0..width[ do
112 row.add(0x00FFFFFF)
113 end
114 self.data[x] = row
115 end
116 end
117
118 # Creates an instance of Bitmap by loading an existing image file
119 init load(path: String)
120 do
121 self.file_path = path
122 var temp = path.file_extension
123 if temp != null then
124 self.file_extension = temp
125 else
126 self.file_extension = ""
127 end
128 var fileReader = new FileReader.open(path)
129
130 # =============== Bitmap header ================
131 for x in [0..13] do
132 var b = fileReader.read_byte
133 if b == null then
134 return
135 end
136 bitmap_header[x] = b.to_i
137 end
138 self.file_size = get_value(bitmap_header.subarray(2, 4))
139 self.data_offset = get_value(bitmap_header.subarray(10, 4))
140
141 # =============== DIB header ================
142 for x in [0..39] do
143 var b = fileReader.read_byte
144 if b == null then return
145 dib_header[x] = b.to_i
146 end
147 var dib_size = get_value(dib_header.subarray(0, 4))
148 # only support BITMAPINFOHEADER
149 if dib_size != 40 then
150 print "This type of bitmap is not supported"
151 fileReader.close
152 return
153 end
154
155 self.width = get_value(dib_header.subarray(4, 4))
156 self.height = get_value(dib_header.subarray(8, 4))
157 self.bits_per_pixel = get_value(dib_header.subarray(14, 2))
158 self.image_size = get_value(dib_header.subarray(20, 4))
159
160 if self.bits_per_pixel != 24 then
161 print "Only full color bitmaps are supported"
162 fileReader.close
163 return
164 end
165
166 if self.bits_per_pixel == 24 then
167 # assert self.image_size + 54 <= self.file_size
168 # start loading image data, for now assume no padding
169 for x in [0..self.height[
170 do
171 var row = new Array[Int].with_capacity(self.width)
172 for y in [0..self.width[
173 do
174 var bts = fileReader.read_bytes(3)
175 if bts.length != 3 then return
176 var red = bts[0] << 16
177 var green = bts[1] << 8
178 var blue = bts[2]
179 row.add(red.to_i + green.to_i + blue.to_i)
180 end
181 self.data.add(row)
182 end
183 end
184 fileReader.close
185 end #end of load_from_file method
186
187 # Converts the value contained in two or four bytes into an Int. Since Nit
188 # does not have a byte type, Int is used
189 private fun get_value(array: Array[Int]): Int
190 do
191 var value = 0
192 for x in [0..array.length[ do
193 value += array[x] * 256.to_f.pow(x.to_f).to_i
194 end
195 return value
196 end
197
198 # Converts the value in an Int to four bytes. Since Nit does not have a byte
199 # type, Int is used.
200 private fun set_value(array: Array[Int], start_index: Int, value: Int)
201 do
202 array[start_index] = value & 0x000000FF
203 array[start_index + 1] = (value >> 8) & 0x000000FF
204 array[start_index + 2] = (value >> 16) & 0x000000FF
205 array[start_index + 3] = (value >> 24) & 0x000000FF
206 end
207
208 # Saves the bitmap into a file
209 fun save(path: String)
210 do
211 var fw = new FileWriter.open(path)
212 # Write bitmap header
213 for x in [0..self.bitmap_header.length[ do
214 fw.write(self.bitmap_header[x].code_point.to_s)
215 end
216 # Write dib header
217 for x in [0..self.dib_header.length[ do
218 fw.write(self.dib_header[x].code_point.to_s)
219 end
220 # Write color table (if any)
221 # Write data (no padding for now)
222 for x in [0..self.height[ do
223 var row = self.data[x]
224 for y in [0..self.width[ do
225 var pixel = row[y]
226 var red = pixel >> 16
227 var green = (pixel & 0x00FF00) >> 8
228 var blue = pixel & 0x000000FF
229 fw.write(red.code_point.to_s)
230 fw.write(green.code_point.to_s)
231 fw.write(blue.code_point.to_s)
232 end
233 end
234 fw.close
235 end #end of save
236
237 # Converts the bitmap to grayscale by manipulating each individual pixel
238 fun grayscale
239 do
240 for x in [0..self.height[ do
241 var row = self.data[x]
242 for y in [0..self.width[ do
243 var pixel = row[y]
244 var red = pixel >> 16
245 var green = (pixel & 0x00FF00) >> 8
246 var blue = pixel & 0x000000FF
247 var lum = (0.2126 * red.to_f + 0.7152 * green.to_f + 0.0722 * blue.to_f).to_i
248 pixel = lum * 256 * 256 + lum * 256 + lum
249 self.data[x][y] = pixel
250 end
251 end
252 end
253
254 # Sets an individual pixel, where position (0, 0) represents the top-left pixel.
255 fun set_pixel(x: Int, y: Int, color: Int)
256 do
257 if x >= 0 and y >= 0 and x < self.width and y < self.height then
258 # Since a bitmap stores its rows of pixels upside-down, y is mapped to
259 # height - 1 - y to make (0, 0) the top-left pixel
260 self.data[self.height - 1 - y][x] = color
261 end
262 end
263 end #end of class Bitmap