From: Budi Kurniawan Date: Sun, 19 Apr 2015 14:11:25 +0000 (-0400) Subject: Bitmap class as part of the bitmap library X-Git-Tag: v0.7.5~60^2 X-Git-Url: http://nitlanguage.org Bitmap class as part of the bitmap library Signed-off-by: Budi Kurniawan --- diff --git a/lib/bitmap/bitmap.nit b/lib/bitmap/bitmap.nit new file mode 100644 index 0000000..0343b8e --- /dev/null +++ b/lib/bitmap/bitmap.nit @@ -0,0 +1,261 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2015 Budi Kurniawan +# +# 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. + +# The Bitmap class represents a 24-bit bitmap image. An instance can be constructed +# by calling load or with_size: +# +# new Bitmap.load(path_to_a_bmp_file) +# new Bitmap.with_size(400, 300) +# +# The width and height attributes contain the image's width and height, +# respectively. +# +# Individual pixels can be manipulated by calling the set_pixel function: +# +# set_pixel(x, y, color) +# +# The no-argument grayscale function converts the bitmap to grayscale and is an +# implementation of this Rossetacode task: +# http://rosettacode.org/wiki/Grayscale_image +# +# Finally, the bitmap can be saved to a file by calling save: +# +# save(path_to_a_file) +# +# For information on the bitmap format, see +# http://en.wikipedia.org/wiki/BMP_file_format +# +# A module containing classes for manipulating bitmap images +module bitmap +# +# A class that represents a Bitmap image +class Bitmap + # The file path if this Bitmap is created by loading a bitmap file + var file_path: String is noinit + + # The file extension if this Bitmap is created by loading a bitmap file + var file_extension: String is noinit + + # The file size if this Bitmap is created by loading a bitmap file + var file_size: Int is noinit + + # The image width in pixels + var width: Int is noinit + + # The image height in pixels + var height: Int is noinit + + # The offset of image data. Typically, 54 + private var data_offset: Int is noinit + + # The number of bits representing a pixel. Currently only 24-bit bitmaps are supported + private var bits_per_pixel: Int is noinit + + # The size of images. For a 24-bit bitmap, this is equal to (3*width*height) + private var image_size: Int is noinit + + # 14-byte bitmap header + private var bitmap_header: Array[Int] = [66, 77, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0] + + # 40-byte dib header + private var dib_header: Array[Int] = [40, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 24, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + # The image data, each cell contains a pixel. + # The first 8 least-significant bits represent the red component, + # the next 8 least-significant bits represent the green component, + # and the next 8 bit represent the blue component. + # The last 8 most-significant bits are not used + private var data = new Array[Array[Int]] + + # Constructs a Bitmap object by passing the image's width and height (in pixels) + init with_size(width: Int, height: Int) + do + self.width = width + self.height = height + self.bits_per_pixel = 24 + self.data_offset = 54 + #set width and height + + self.set_value(dib_header, 4, width) + self.set_value(dib_header, 8, height) + #set file size + var file_size = 3 * width * height + 54 + self.set_value(bitmap_header, 2, file_size) + + #init pixel data + self.data = new Array[Array[Int]].with_capacity(height) + for x in [0..height[ do + var row = new Array[Int].with_capacity(width) + for y in [0..width[ do + row.add(0x00FFFFFF) + end + self.data[x] = row + end + end + + # Creates an instance of Bitmap by loading an existing image file + init load(path: String) + do + self.file_path = path + var temp = path.file_extension + if temp != null then + self.file_extension = temp + else + self.file_extension = "" + end + var fileReader = new FileReader.open(path) + + # =============== Bitmap header ================ + for x in [0..13] do + bitmap_header[x] = fileReader.read(1)[0].ascii + end + self.file_size = get_value(bitmap_header.subarray(2, 4)) + self.data_offset = get_value(bitmap_header.subarray(10, 4)) + + # =============== DIB header ================ + for x in [0..39] do + dib_header[x] = fileReader.read(1)[0].ascii + end + var dib_size = get_value(dib_header.subarray(0, 4)) + # only support BITMAPINFOHEADER + if dib_size != 40 then + print "This type of bitmap is not supported" + fileReader.close + return + end + + self.width = get_value(dib_header.subarray(4, 4)) + self.height = get_value(dib_header.subarray(8, 4)) + self.bits_per_pixel = get_value(dib_header.subarray(14, 2)) + self.image_size = get_value(dib_header.subarray(20, 4)) + + if self.bits_per_pixel != 24 then + print "Only full color bitmaps are supported" + fileReader.close + return + end + + if self.bits_per_pixel == 24 then + # assert self.image_size + 54 <= self.file_size + # start loading image data, for now assume no padding + for x in [0..self.height[ + do + var row = new Array[Int].with_capacity(self.width) + for y in [0..self.width[ + do + var red = fileReader.read(1)[0].ascii * 256 * 256 + var green = fileReader.read(1)[0].ascii * 256 + var blue = fileReader.read(1)[0].ascii + row.add(red + green + blue) + end + self.data.add(row) + end + end + fileReader.close + end #end of load_from_file method + + # Reads in a series of bytes from the FileReader when loading a Bitmap from a file + # FileReader.read(1) is used due to https://github.com/privat/nit/issues/1264 + private fun read_chars(fr: FileReader, howMany: Int): String + do + var s = "" + for x in [1..howMany] + do + s += fr.read(1) + end + return s + end + + # Converts the value contained in two or four bytes into an Int. Since Nit + # does not have a byte type, Int is used + private fun get_value(array: Array[Int]): Int + do + var value = 0 + for x in [0..array.length[ do + value += array[x] * 256.to_f.pow(x.to_f).to_i + end + return value + end + + # Converts the value in an Int to four bytes. Since Nit does not have a byte + # type, Int is used. + private fun set_value(array: Array[Int], start_index: Int, value: Int) + do + array[start_index] = value.bin_and(0x000000FF) + array[start_index + 1] = value.rshift(8).bin_and(0x000000FF) + array[start_index + 2] = value.rshift(16).bin_and(0x000000FF) + array[start_index + 3] = value.rshift(24).bin_and(0x000000FF) + end + + # Saves the bitmap into a file + fun save(path: String) + do + var fw = new FileWriter.open(path) + # Write bitmap header + for x in [0..self.bitmap_header.length[ do + fw.write(self.bitmap_header[x].ascii.to_s) + end + # Write dib header + for x in [0..self.dib_header.length[ do + fw.write(self.dib_header[x].ascii.to_s) + end + # Write color table (if any) + # Write data (no padding for now) + for x in [0..self.height[ do + var row = self.data[x] + for y in [0..self.width[ do + var pixel = row[y] + var red = pixel.rshift(16) + var green = pixel.bin_and(0x00FF00).rshift(8) + var blue = pixel.bin_and(0x000000FF) + fw.write(red.ascii.to_s) + fw.write(green.ascii.to_s) + fw.write(blue.ascii.to_s) + end + end + fw.close + end #end of save + + # Converts the bitmap to grayscale by manipulating each individual pixel + fun grayscale + do + for x in [0..self.height[ do + var row = self.data[x] + for y in [0..self.width[ do + var pixel = row[y] + var red = pixel.rshift(16) + var green = pixel.bin_and(0x00FF00).rshift(8) + var blue = pixel.bin_and(0x000000FF) + var lum = (0.2126 * red.to_f + 0.7152 * green.to_f + 0.0722 * blue.to_f).to_i + pixel = lum * 256 * 256 + lum * 256 + lum + self.data[x][y] = pixel + end + end + end + + # Sets an individual pixel, where position (0, 0) represents the top-left pixel. + fun set_pixel(x: Int, y: Int, color: Int) + do + if x >= 0 and y >= 0 and x < self.width and y < self.height then + # Since a bitmap stores its rows of pixels upside-down, y is mapped to + # height - 1 - y to make (0, 0) the top-left pixel + self.data[self.height - 1 - y][x] = color + end + end +end #end of class Bitmap diff --git a/lib/bitmap/test_bitmap.nit b/lib/bitmap/test_bitmap.nit new file mode 100644 index 0000000..4475689 --- /dev/null +++ b/lib/bitmap/test_bitmap.nit @@ -0,0 +1,44 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2015 Budi Kurniawan +# +# 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. + +# This file tests the Bitmap class in the bitmap module +# It has to be called by passing two arguments: +# +# - An input bitmap file +# - An output bitmap file +# +# It converts the input bitmap to grayscale and save it to the specified +# output file. In addition, it creates a blank.bmp file in the current directory. +# +# +# A module for testing the classes in the bitmap module +module test_bitmap is test_suite + +import bitmap +import test_suite + +class TestBitmap + super TestSuite + + fun test_grayscale do + var bitmap = new Bitmap.with_size(400, 300) + for y in [0..300] do + for x in [0..200] do bitmap.set_pixel(x, y, 0x0077AAAA) + end + bitmap.grayscale + bitmap.save("./blank.bmp") + end +end