Bitmap.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. # -*- coding: windows-1251 -*-
  2. # Portions are Copyright (C) 2005 Roman V. Kiseliov
  3. # Portions are Copyright (c) 2004 Evgeny Filatov <fufff@users.sourceforge.net>
  4. # Portions are Copyright (c) 2002-2004 John McNamara (Perl Spreadsheet::WriteExcel)
  5. from .BIFFRecords import BiffRecord
  6. from struct import pack, unpack
  7. def _size_col(sheet, col):
  8. return sheet.col_width(col)
  9. def _size_row(sheet, row):
  10. return sheet.row_height(row)
  11. def _position_image(sheet, row_start, col_start, x1, y1, width, height):
  12. """Calculate the vertices that define the position of the image as required by
  13. the OBJ record.
  14. +------------+------------+
  15. | A | B |
  16. +-----+------------+------------+
  17. | |(x1,y1) | |
  18. | 1 |(A1)._______|______ |
  19. | | | | |
  20. | | | | |
  21. +-----+----| BITMAP |-----+
  22. | | | | |
  23. | 2 | |______________. |
  24. | | | (B2)|
  25. | | | (x2,y2)|
  26. +---- +------------+------------+
  27. Example of a bitmap that covers some of the area from cell A1 to cell B2.
  28. Based on the width and height of the bitmap we need to calculate 8 vars:
  29. col_start, row_start, col_end, row_end, x1, y1, x2, y2.
  30. The width and height of the cells are also variable and have to be taken into
  31. account.
  32. The values of col_start and row_start are passed in from the calling
  33. function. The values of col_end and row_end are calculated by subtracting
  34. the width and height of the bitmap from the width and height of the
  35. underlying cells.
  36. The vertices are expressed as a percentage of the underlying cell width as
  37. follows (rhs values are in pixels):
  38. x1 = X / W *1024
  39. y1 = Y / H *256
  40. x2 = (X-1) / W *1024
  41. y2 = (Y-1) / H *256
  42. Where: X is distance from the left side of the underlying cell
  43. Y is distance from the top of the underlying cell
  44. W is the width of the cell
  45. H is the height of the cell
  46. Note: the SDK incorrectly states that the height should be expressed as a
  47. percentage of 1024.
  48. col_start - Col containing upper left corner of object
  49. row_start - Row containing top left corner of object
  50. x1 - Distance to left side of object
  51. y1 - Distance to top of object
  52. width - Width of image frame
  53. height - Height of image frame
  54. """
  55. # Adjust start column for offsets that are greater than the col width
  56. while x1 >= _size_col(sheet, col_start):
  57. x1 -= _size_col(sheet, col_start)
  58. col_start += 1
  59. # Adjust start row for offsets that are greater than the row height
  60. while y1 >= _size_row(sheet, row_start):
  61. y1 -= _size_row(sheet, row_start)
  62. row_start += 1
  63. # Initialise end cell to the same as the start cell
  64. row_end = row_start # Row containing bottom right corner of object
  65. col_end = col_start # Col containing lower right corner of object
  66. width = width + x1 - 1
  67. height = height + y1 - 1
  68. # Subtract the underlying cell widths to find the end cell of the image
  69. while (width >= _size_col(sheet, col_end)):
  70. width -= _size_col(sheet, col_end)
  71. col_end += 1
  72. # Subtract the underlying cell heights to find the end cell of the image
  73. while (height >= _size_row(sheet, row_end)):
  74. height -= _size_row(sheet, row_end)
  75. row_end += 1
  76. # Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
  77. # with zero height or width.
  78. if ((_size_col(sheet, col_start) == 0) or (_size_col(sheet, col_end) == 0)
  79. or (_size_row(sheet, row_start) == 0) or (_size_row(sheet, row_end) == 0)):
  80. return
  81. # Convert the pixel values to the percentage value expected by Excel
  82. x1 = int(float(x1) / _size_col(sheet, col_start) * 1024)
  83. y1 = int(float(y1) / _size_row(sheet, row_start) * 256)
  84. # Distance to right side of object
  85. x2 = int(float(width) / _size_col(sheet, col_end) * 1024)
  86. # Distance to bottom of object
  87. y2 = int(float(height) / _size_row(sheet, row_end) * 256)
  88. return (col_start, x1, row_start, y1, col_end, x2, row_end, y2)
  89. class ObjBmpRecord(BiffRecord):
  90. _REC_ID = 0x005D # Record identifier
  91. def __init__(self, row, col, sheet, im_data_bmp, x, y, scale_x, scale_y):
  92. # Scale the frame of the image.
  93. width = im_data_bmp.width * scale_x
  94. height = im_data_bmp.height * scale_y
  95. # Calculate the vertices of the image and write the OBJ record
  96. coordinates = _position_image(sheet, row, col, x, y, width, height)
  97. # print coordinates
  98. col_start, x1, row_start, y1, col_end, x2, row_end, y2 = coordinates
  99. """Store the OBJ record that precedes an IMDATA record. This could be generalise
  100. to support other Excel objects.
  101. """
  102. cObj = 0x0001 # Count of objects in file (set to 1)
  103. OT = 0x0008 # Object type. 8 = Picture
  104. id = 0x0001 # Object ID
  105. grbit = 0x0614 # Option flags
  106. colL = col_start # Col containing upper left corner of object
  107. dxL = x1 # Distance from left side of cell
  108. rwT = row_start # Row containing top left corner of object
  109. dyT = y1 # Distance from top of cell
  110. colR = col_end # Col containing lower right corner of object
  111. dxR = x2 # Distance from right of cell
  112. rwB = row_end # Row containing bottom right corner of object
  113. dyB = y2 # Distance from bottom of cell
  114. cbMacro = 0x0000 # Length of FMLA structure
  115. Reserved1 = 0x0000 # Reserved
  116. Reserved2 = 0x0000 # Reserved
  117. icvBack = 0x09 # Background colour
  118. icvFore = 0x09 # Foreground colour
  119. fls = 0x00 # Fill pattern
  120. fAuto = 0x00 # Automatic fill
  121. icv = 0x08 # Line colour
  122. lns = 0xff # Line style
  123. lnw = 0x01 # Line weight
  124. fAutoB = 0x00 # Automatic border
  125. frs = 0x0000 # Frame style
  126. cf = 0x0009 # Image format, 9 = bitmap
  127. Reserved3 = 0x0000 # Reserved
  128. cbPictFmla = 0x0000 # Length of FMLA structure
  129. Reserved4 = 0x0000 # Reserved
  130. grbit2 = 0x0001 # Option flags
  131. Reserved5 = 0x0000 # Reserved
  132. data = pack("<L", cObj)
  133. data += pack("<H", OT)
  134. data += pack("<H", id)
  135. data += pack("<H", grbit)
  136. data += pack("<H", colL)
  137. data += pack("<H", dxL)
  138. data += pack("<H", rwT)
  139. data += pack("<H", dyT)
  140. data += pack("<H", colR)
  141. data += pack("<H", dxR)
  142. data += pack("<H", rwB)
  143. data += pack("<H", dyB)
  144. data += pack("<H", cbMacro)
  145. data += pack("<L", Reserved1)
  146. data += pack("<H", Reserved2)
  147. data += pack("<B", icvBack)
  148. data += pack("<B", icvFore)
  149. data += pack("<B", fls)
  150. data += pack("<B", fAuto)
  151. data += pack("<B", icv)
  152. data += pack("<B", lns)
  153. data += pack("<B", lnw)
  154. data += pack("<B", fAutoB)
  155. data += pack("<H", frs)
  156. data += pack("<L", cf)
  157. data += pack("<H", Reserved3)
  158. data += pack("<H", cbPictFmla)
  159. data += pack("<H", Reserved4)
  160. data += pack("<H", grbit2)
  161. data += pack("<L", Reserved5)
  162. self._rec_data = data
  163. def _process_bitmap(bitmap):
  164. """Convert a 24 bit bitmap into the modified internal format used by Windows.
  165. This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
  166. MSDN library.
  167. """
  168. # Open file and binmode the data in case the platform needs it.
  169. with open(bitmap, "rb") as fh:
  170. # Slurp the file into a string.
  171. data = fh.read()
  172. return _process_bitmap_data(data)
  173. def _process_bitmap_data(data):
  174. # Check that the file is big enough to be a bitmap.
  175. if len(data) <= 0x36:
  176. raise Exception("bitmap doesn't contain enough data.")
  177. # The first 2 bytes are used to identify the bitmap.
  178. if (data[:2] != b"BM"):
  179. raise Exception("bitmap doesn't appear to to be a valid bitmap image.")
  180. # Remove bitmap data: ID.
  181. data = data[2:]
  182. # Read and remove the bitmap size. This is more reliable than reading
  183. # the data size at offset 0x22.
  184. #
  185. size = unpack("<L", data[:4])[0]
  186. size -= 0x36 # Subtract size of bitmap header.
  187. size += 0x0C # Add size of BIFF header.
  188. data = data[4:]
  189. # Remove bitmap data: reserved, offset, header length.
  190. data = data[12:]
  191. # Read and remove the bitmap width and height. Verify the sizes.
  192. width, height = unpack("<LL", data[:8])
  193. data = data[8:]
  194. if (width > 0xFFFF):
  195. raise Exception("bitmap: largest image width supported is 65k.")
  196. if (height > 0xFFFF):
  197. raise Exception("bitmap: largest image height supported is 65k.")
  198. # Read and remove the bitmap planes and bpp data. Verify them.
  199. planes, bitcount = unpack("<HH", data[:4])
  200. data = data[4:]
  201. if (bitcount != 24):
  202. raise Exception("bitmap isn't a 24bit true color bitmap.")
  203. if (planes != 1):
  204. raise Exception("bitmap: only 1 plane supported in bitmap image.")
  205. # Read and remove the bitmap compression. Verify compression.
  206. compression = unpack("<L", data[:4])[0]
  207. data = data[4:]
  208. if (compression != 0):
  209. raise Exception("bitmap: compression not supported in bitmap image.")
  210. # Remove bitmap data: data size, hres, vres, colours, imp. colours.
  211. data = data[20:]
  212. # Add the BITMAPCOREHEADER data
  213. header = pack("<LHHHH", 0x000c, width, height, 0x01, 0x18)
  214. data = header + data
  215. return (width, height, size, data)
  216. class ImRawDataBmpRecord(BiffRecord):
  217. _REC_ID = 0x007F
  218. def __init__(self, data):
  219. """Insert a 24bit bitmap image in a worksheet. The main record required is
  220. IMDATA but it must be proceeded by a OBJ record to define its position.
  221. """
  222. BiffRecord.__init__(self)
  223. self.width, self.height, self.size, data = _process_bitmap_data(data)
  224. self._write_imdata(data)
  225. def _write_imdata(self, data):
  226. # Write the IMDATA record to store the bitmap data
  227. cf = 0x09
  228. env = 0x01
  229. lcb = self.size
  230. self._rec_data = pack("<HHL", cf, env, lcb) + data
  231. class ImDataBmpRecord(ImRawDataBmpRecord):
  232. def __init__(self, filename):
  233. """Insert a 24bit bitmap image in a worksheet. The main record required is
  234. IMDATA but it must be proceeded by a OBJ record to define its position.
  235. """
  236. BiffRecord.__init__(self)
  237. self.width, self.height, self.size, data = _process_bitmap(filename)
  238. self._write_imdata(data)