lib/write_xlsx/utility.rb in write_xlsx-1.11.2 vs lib/write_xlsx/utility.rb in write_xlsx-1.12.0

- old
+ new

@@ -986,9 +986,181 @@ # Write the <a:endParaRPr> element. # def write_a_end_para_rpr # :nodoc: @writer.empty_tag('a:endParaRPr', [%w[lang en-US]]) end + + # + # Extract information from the image file such as dimension, type, filename, + # and extension. Also keep track of previously seen images to optimise out + # any duplicates. + # + def get_image_properties(filename) + # Note the image_id, and previous_images mechanism isn't currently used. + x_dpi = 96 + y_dpi = 96 + + workbook = @workbook || self + + # Open the image file and import the data. + data = File.binread(filename) + md5 = Digest::MD5.hexdigest(data) + if data.unpack1('x A3') == 'PNG' + # Test for PNGs. + type, width, height, x_dpi, y_dpi = process_png(data) + workbook.image_types[:png] = 1 + elsif data.unpack1('n') == 0xFFD8 + # Test for JPEG files. + type, width, height, x_dpi, y_dpi = process_jpg(data, filename) + workbook.image_types[:jpeg] = 1 + elsif data.unpack1('A4') == 'GIF8' + # Test for GIFs. + type, width, height, x_dpi, y_dpi = process_gif(data, filename) + workbook.image_types[:gif] = 1 + elsif data.unpack1('A2') == 'BM' + # Test for BMPs. + type, width, height = process_bmp(data, filename) + workbook.image_types[:bmp] = 1 + else + # TODO. Add Image::Size to support other types. + raise "Unsupported image format for file: #{filename}\n" + end + + # Set a default dpi for images with 0 dpi. + x_dpi = 96 if x_dpi == 0 + y_dpi = 96 if y_dpi == 0 + + [type, width, height, File.basename(filename), x_dpi, y_dpi, md5] + end + + # + # Extract width and height information from a PNG file. + # + def process_png(data) + type = 'png' + width = 0 + height = 0 + x_dpi = 96 + y_dpi = 96 + + offset = 8 + data_length = data.size + + # Search through the image data to read the height and width in th the + # IHDR element. Also read the DPI in the pHYs element. + while offset < data_length + + length = data[offset + 0, 4].unpack1("N") + png_type = data[offset + 4, 4].unpack1("A4") + + case png_type + when "IHDR" + width = data[offset + 8, 4].unpack1("N") + height = data[offset + 12, 4].unpack1("N") + when "pHYs" + x_ppu = data[offset + 8, 4].unpack1("N") + y_ppu = data[offset + 12, 4].unpack1("N") + units = data[offset + 16, 1].unpack1("C") + + if units == 1 + x_dpi = x_ppu * 0.0254 + y_dpi = y_ppu * 0.0254 + end + end + + offset = offset + length + 12 + + break if png_type == "IEND" + end + raise "#{filename}: no size data found in png image.\n" unless height + + [type, width, height, x_dpi, y_dpi] + end + + def process_jpg(data, filename) + type = 'jpeg' + x_dpi = 96 + y_dpi = 96 + + offset = 2 + data_length = data.bytesize + + # Search through the image data to read the JPEG markers. + while offset < data_length + marker = data[offset + 0, 2].unpack1("n") + length = data[offset + 2, 2].unpack1("n") + + # Read the height and width in the 0xFFCn elements + # (Except C4, C8 and CC which aren't SOF markers). + if (marker & 0xFFF0) == 0xFFC0 && + marker != 0xFFC4 && marker != 0xFFCC + height = data[offset + 5, 2].unpack1("n") + width = data[offset + 7, 2].unpack1("n") + end + + # Read the DPI in the 0xFFE0 element. + if marker == 0xFFE0 + units = data[offset + 11, 1].unpack1("C") + x_density = data[offset + 12, 2].unpack1("n") + y_density = data[offset + 14, 2].unpack1("n") + + if units == 1 + x_dpi = x_density + y_dpi = y_density + elsif units == 2 + x_dpi = x_density * 2.54 + y_dpi = y_density * 2.54 + end + end + + offset += length + 2 + break if marker == 0xFFDA + end + + raise "#{filename}: no size data found in jpeg image.\n" unless height + + [type, width, height, x_dpi, y_dpi] + end + + # + # Extract width and height information from a GIF file. + # + def process_gif(data, filename) + type = 'gif' + x_dpi = 96 + y_dpi = 96 + + width = data[6, 2].unpack1("v") + height = data[8, 2].unpack1("v") + + raise "#{filename}: no size data found in gif image.\n" if height.nil? + + [type, width, height, x_dpi, y_dpi] + end + + # Extract width and height information from a BMP file. + def process_bmp(data, filename) # :nodoc: + type = 'bmp' + + # Check that the file is big enough to be a bitmap. + raise "#{filename} doesn't contain enough data." if data.bytesize <= 0x36 + + # Read the bitmap width and height. Verify the sizes. + width, height = data.unpack("x18 V2") + raise "#{filename}: largest image width #{width} supported is 65k." if width > 0xFFFF + raise "#{filename}: largest image height supported is 65k." if height > 0xFFFF + + # Read the bitmap planes and bpp data. Verify them. + planes, bitcount = data.unpack("x26 v2") + raise "#{filename} isn't a 24bit true color bitmap." unless bitcount == 24 + raise "#{filename}: only 1 plane supported in bitmap image." unless planes == 1 + + # Read the bitmap compression. Verify compression. + compression = data.unpack1("x30 V") + raise "#{filename}: compression not supported in bitmap image." unless compression == 0 + + [type, width, height] + end end module WriteDPtPoint # # Write an individual <c:dPt> element. Override the parent method to add