1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
for y in 0...height for x in 0...width # BGR => RGB & miror y z = (height-y-1)*width*3 + 3*x corrected << data[z+2] corrected << data[z+1] corrected << data[z ] corrected << 255 # Add alpha value end for x in width...real_size corrected << 0 corrected << 0 corrected << 0 corrected << 0 end end for y in height...real_size for x in 0...real_size corrected << 0 corrected << 0 corrected << 0 corrected << 0 end end
Refactorings
No refactoring yet !
Ben Marini
January 31, 2010, January 31, 2010 20:33, permalink
I'd like to help out, but I can't quite figure out what your code snippet is doing. Can you provide a more complete code snippet? For instance, define `height`, `width`, `real_size`, `data`, and `corrected`?
darkleo.myopenid.com
January 31, 2010, January 31, 2010 21:36, permalink
I read `data`, `width` and `height` directly from a bmp file.
To use it, OpenGL requires that the image size (on each dimension) be a power of 2.
So `real_size` is the closest power of 2 who match the image size.
The original data is encoded in BGR, and i need it in RGBA.
Finally i need to reverse the rows, because the data "begin from the end"
I give an example with a very simple bitmap, 3 rows, 2 columns :
Red/Red
Green/Green
Blue/Blue
With this 3*2 bitmap :
data = [ 255, 0, 0, # 1st pixel, blue (third row)
255, 0, 0, # 2st pixel, blue
0, 255, 0, # 3nd pixel, green (second row)
0, 255, 0, # 4nd pixel, green
0, 0, 255, # 5rd pixel, red (first row)
0, 0, 255, # 6rd pixel, red
]
width = 2
height = 3
We calculate :
real_size = 4 # closest power of 2
And we obtain a 4*4 bitmap :
corrected = [ 255, 0, 0, 255 # 1st pixel, red (first row)
255, 0, 0, 255 # 2st pixel, red
0, 0, 0, 0 # Empty
0, 0, 0, 0 # Empty
0, 255, 0, 255 # 3nd pixel, green (second row)
0, 255, 0, 255 # 4nd pixel, green
0, 0, 0, 0 # Empty
0, 0, 0, 0 # Empty
0, 0, 255, 255 # 5rd pixel, blue (third row)
0, 0, 255, 255 # 6rd pixel, blue
0, 0, 0, 0 # Empty
0, 0, 0, 0 # Empty
0, 0, 0, 0 # Empty (last row)
0, 0, 0, 0 # Empty
0, 0, 0, 0 # Empty
0, 0, 0, 0 # Empty
]
I give also the entire method.
Thanks a lot by advance !
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
def self.load_bmp name fail unless FileTest.exist?('Media/'+name) file = File.open('Media/'+name, 'rb') struct = {:name => name} file.seek(18, IO::SEEK_CUR) # Read width and height struct[:width] = file.read(4).unpack('i')[0] struct[:height] = file.read(4).unpack('i')[0] # Calculate closest power of 2 struct[:real_size] = 2 struct[:real_size] *= 2 while struct[:real_size] < struct[:width] struct[:real_size] *= 2 while struct[:real_size] < struct[:height] file.seek(28, IO::SEEK_CUR) # Read data from file size = struct[:width]*struct[:height]*3 data = file.read(size).unpack('C*') file.close corrected = [] # BGR => RGB & miror y for y in 0...(struct[:height]) for x in 0...(struct[:width]) z = (struct[:height]-y-1)*struct[:width]*3 + 3*x corrected << data[z+2] corrected << data[z+1] corrected << data[z ] corrected << 255 # Add alpha value end for x in struct[:width]...struct[:real_size] corrected << 0 corrected << 0 corrected << 0 corrected << 0 end end for y in struct[:height]...struct[:real_size] for x in 0...struct[:real_size] corrected << 0 corrected << 0 corrected << 0 corrected << 0 end end # Return corrected texture struct[:data] = corrected.pack('C*') return struct end
Ants
February 1, 2010, February 01, 2010 23:48, permalink
OpenGL 2.0 and up supports textures that are not powers of two.
Ben Marini
February 3, 2010, February 03, 2010 16:09, permalink
Here's my functional version, with some test code.
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
module Bitmap def self.bgr_to_rgba(raw_bgr_array, width, height, new_dimension) width_padding = new_dimension - width height_padding = new_dimension - height # First convert to array of Pixel::BGR objects raw_bgr_array.enum_for(:each_slice, 3).map { |a| Pixel::BGR.new(*a) }. reverse. # Reverse it map { |bgr| bgr.to_rgba }. # Map to array of Pixel::RGBA objects enum_for(:each_slice, width).inject([]) { |memo, row| memo << row }. # Create a 2d array based on width map { |row| row.concat( [Pixel::RGBA.blank] * width_padding ) }.concat( [ [Pixel::RGBA.blank] * new_dimension ] * height_padding ). # Change dimensions of 2d array, adding blanks flatten.map { |rgba| rgba.to_a }.flatten # Flatten everything back into an array of integers end module Pixel class RGBA < Struct.new(:red, :green, :blue, :alpha) def self.blank new(0,0,0,0) end def to_a [red, green, blue, alpha] end end class BGR < Struct.new(:blue, :green, :red) def to_rgba RGBA.new(red, green, blue, 255) end end end end if __FILE__ == $0 require "test/unit" class TestBgrToRgba < Test::Unit::TestCase def test_bgr_to_rgba width = 2 height = 3 real_size = 4 # closest power of 2 data = [ 255, 0, 0, # 1st pixel, blue (third row) 255, 0, 0, # 2st pixel, blue 0, 255, 0, # 3nd pixel, green (second row) 0, 255, 0, # 4nd pixel, green 0, 0, 255, # 5rd pixel, red (first row) 0, 0, 255, # 6rd pixel, red ] corrected = [ 255, 0, 0, 255, # 1st pixel, red (first row) 255, 0, 0, 255, # 2st pixel, red 0, 0, 0, 0, # Empty 0, 0, 0, 0, # Empty 0, 255, 0, 255, # 3nd pixel, green (second row) 0, 255, 0, 255, # 4nd pixel, green 0, 0, 0, 0, # Empty 0, 0, 0, 0, # Empty 0, 0, 255, 255, # 5rd pixel, blue (third row) 0, 0, 255, 255, # 6rd pixel, blue 0, 0, 0, 0, # Empty 0, 0, 0, 0, # Empty 0, 0, 0, 0, # Empty (last row) 0, 0, 0, 0, # Empty 0, 0, 0, 0, # Empty 0, 0, 0, 0 # Empty ] result = Bitmap.bgr_to_rgba(data, width, height, real_size) assert_equal corrected, result end end end
Ben Marini
February 3, 2010, February 03, 2010 16:09, permalink
Here's my functional version: http://gist.github.com/293713
darkleo.myopenid.com
February 7, 2010, February 07, 2010 21:44, permalink
@Ants : It is obviously much faster with powers of two, at least with ruby-opengl
@Ben Marini : Thank you very much, It will help me a lot, particularly the methods 'map' and 'flatten'
Alex Baranosky
February 11, 2010, February 11, 2010 02:04, permalink
Mine is using Ruby 1.9... I refactored all the code even the stuff loading the bmp from the file. I was not able to test any of that code however so hopefully it is bug free, the spec I show below covers generating the corrected bmp.
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
class BmpCorrector def load_bmp(name) bmp, original_bmp_data_array = create_bmp_from_file(name) bmp.generate_corrected(original_bmp_data_array) end def create_bmp_from_file(name) file_name = "Media/#{name}" fail unless FileTest.exist?(file_name) bmp = nil original_bmp_data_array = open(file_name, 'rb') do |file| width, height = read_width_and_height(file) bmp = Bmp.new(height, width) read_data(height, width, file) end return bmp, original_bmp_data_array end def read_width_and_height(file) file.seek(18, IO::SEEK_CUR) [file.read(4).unpack('i')[0], file.read(4).unpack('i')[0]] end def read_data(height, width, file) file.seek(28, IO::SEEK_CUR) size = width * height * Bmp::BGR_PIXEL_SLICE_WIDTH file.read(size).unpack('C*') end end class Bmp BGR_PIXEL_SLICE_WIDTH = 3 def initialize(height, width) @original_height, @original_width = height, width @new_dimension = closest_power_of_two end def generate_corrected(raw_bgr_array) right_padding = @new_dimension - @original_width bottom_padding = @new_dimension - @original_height pixels = pixels_from_raw_bgr_data(raw_bgr_array) pixel_rows_w_out_padding = pixel_2d_array(pixels) add_padding(pixel_rows_w_out_padding, bottom_padding, right_padding) end def pixels_from_raw_bgr_data(raw_bgr_array) raw_bgr_array.each_slice(BGR_PIXEL_SLICE_WIDTH). collect { |slice| Pixel.from_bgr(*slice) }. reverse end def pixel_2d_array(pixels) pixels.each_slice(@original_width).inject([]) { |result, row| result << row } end def add_padding(pixel_rows, bottom_padding, right_padding) corrected_array = pixel_rows.collect {|row| row + Pixel.blanks(right_padding) } corrected_array += Pixel.blanks(@new_dimension * bottom_padding) corrected_array.flatten.collect(&:to_a).flatten end def closest_power_of_two result = 2 result *= 2 while (result < @original_height || result < @original_width) result end end class Pixel def initialize(red = 0, green = 0, blue = 0, alpha = 0) @red, @green, @blue, @alpha = red, green, blue, alpha end def self.from_bgr(blue, green, red) Pixel.new(red, green, blue, 255) end def self.blanks(amount = 1) Array.new(amount) { Pixel.new } end def to_a [@red, @green, @blue, @alpha] end end BmpCorrector.new.load_bmp("filename.....")
Spec File
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
require "spec" require 'corrector' describe Bmp do width = 2 height = 3 data = [ 255, 0, 0, # 1st pixel, blue (third row) 255, 0, 0, # 2st pixel, blue 0, 255, 0, # 3nd pixel, green (second row) 0, 255, 0, # 4nd pixel, green 0, 0, 255, # 5rd pixel, red (first row) 0, 0, 255, # 6rd pixel, red ] expected_corrected = [ 255, 0, 0, 255, # 1st pixel, red (first row) 255, 0, 0, 255, # 2st pixel, red 0, 0, 0, 0, # Empty 0, 0, 0, 0, # Empty 0, 255, 0, 255, # 3nd pixel, green (second row) 0, 255, 0, 255, # 4nd pixel, green 0, 0, 0, 0, # Empty 0, 0, 0, 0, # Empty 0, 0, 255, 255, # 5rd pixel, blue (third row) 0, 0, 255, 255, # 6rd pixel, blue 0, 0, 0, 0, # Empty 0, 0, 0, 0, # Empty 0, 0, 0, 0, # Empty (last row) 0, 0, 0, 0, # Empty 0, 0, 0, 0, # Empty 0, 0, 0, 0 # Empty ] it "should correct the bmp properly" do actual_corrected = Bmp.new(height, width).generate_corrected(data) actual_corrected.should == expected_corrected end end
I need to reformat .bmp data, by adding a lot of '0'
But my code is... horribly ugly.
'data' contain the original data (array)
'corrected' is an empty array
Before process :
[XXXX
XXXX
XXXX]
After process :
[XXXX0000
XXXX0000
XXXX0000
00000000
00000000
00000000]