images2swf.py 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019
  1. # -*- coding: utf-8 -*-
  2. # Copyright (C) 2012, Almar Klein
  3. #
  4. # This code is subject to the (new) BSD license:
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions are met:
  8. # * Redistributions of source code must retain the above copyright
  9. # notice, this list of conditions and the following disclaimer.
  10. # * Redistributions in binary form must reproduce the above copyright
  11. # notice, this list of conditions and the following disclaimer in the
  12. # documentation and/or other materials provided with the distribution.
  13. # * Neither the name of the <organization> nor the
  14. # names of its contributors may be used to endorse or promote products
  15. # derived from this software without specific prior written permission.
  16. #
  17. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  18. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  19. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  20. # ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  21. # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  22. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  23. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  24. # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  26. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. """ Module images2swf
  28. Provides a function (writeSwf) to store a series of PIL images or numpy
  29. arrays in an SWF movie, that can be played on a wide range of OS's.
  30. This module came into being because I wanted to store a series of images
  31. in a movie that can be viewed by other people, and which I can embed in
  32. flash presentations. For writing AVI or MPEG you really need a c/c++
  33. library, and although the filesize is then very small, the quality is
  34. sometimes not adequate. Besides I'd like to be independent of yet another
  35. package. I tried writing animated gif using PIL (which is widely available),
  36. but the quality is so poor because it only allows for 256 different colors.
  37. [EDIT: thanks to Ant1, now the quality of animated gif isn't so bad!]
  38. I also looked into MNG and APNG, two standards similar to the PNG stanard.
  39. Both standards promise exactly what I need. However, hardly any application
  40. can read those formats, and I cannot import them in flash.
  41. Therefore I decided to check out the swf file format, which is very well
  42. documented. This is the result: a pure python module to create an SWF file
  43. that shows a series of images. The images are stored using the DEFLATE
  44. algorithm (same as PNG and ZIP and which is included in the standard Python
  45. distribution). As this compression algorithm is much more effective than
  46. that used in GIF images, we obtain better quality (24 bit colors + alpha
  47. channel) while still producesing smaller files (a test showed ~75%).
  48. Although SWF also allows for JPEG compression, doing so would probably
  49. require a third party library (because encoding JPEG is much harder).
  50. This module requires Python 2.x and numpy.
  51. sources and tools:
  52. - SWF on wikipedia
  53. - Adobes "SWF File Format Specification" version 10
  54. (http://www.adobe.com/devnet/swf/pdf/swf_file_format_spec_v10.pdf)
  55. - swftools (swfdump in specific) for debugging
  56. - iwisoft swf2avi can be used to convert swf to avi/mpg/flv with really
  57. good quality, while file size is reduced with factors 20-100.
  58. A good program in my opinion. The free version has the limitation
  59. of a watermark in the upper left corner.
  60. """
  61. import os
  62. import sys
  63. import time
  64. import zlib
  65. try:
  66. import numpy as np
  67. except ImportError:
  68. np = None
  69. try:
  70. import PIL.Image
  71. except ImportError:
  72. PIL = None
  73. # True if we are running on Python 3.
  74. # Code taken from six.py by Benjamin Peterson (MIT licensed)
  75. import types
  76. PY3 = sys.version_info[0] == 3
  77. if PY3:
  78. string_types = str,
  79. integer_types = int,
  80. class_types = type,
  81. text_type = str
  82. binary_type = bytes
  83. else:
  84. string_types = basestring,
  85. integer_types = (int, long)
  86. class_types = (type, types.ClassType)
  87. text_type = unicode
  88. binary_type = str
  89. # todo: use imageio/FreeImage to support reading JPEG images from SWF?
  90. def checkImages(images):
  91. """ checkImages(images)
  92. Check numpy images and correct intensity range etc.
  93. The same for all movie formats.
  94. """
  95. # Init results
  96. images2 = []
  97. for im in images:
  98. if PIL and isinstance(im, PIL.Image.Image):
  99. # We assume PIL images are allright
  100. images2.append(im)
  101. elif np and isinstance(im, np.ndarray):
  102. # Check and convert dtype
  103. if im.dtype == np.uint8:
  104. images2.append(im) # Ok
  105. elif im.dtype in [np.float32, np.float64]:
  106. theMax = im.max()
  107. if theMax > 128 and theMax < 300:
  108. pass # assume 0:255
  109. else:
  110. im = im.copy()
  111. im[im < 0] = 0
  112. im[im > 1] = 1
  113. im *= 255
  114. images2.append(im.astype(np.uint8))
  115. else:
  116. im = im.astype(np.uint8)
  117. images2.append(im)
  118. # Check size
  119. if im.ndim == 2:
  120. pass # ok
  121. elif im.ndim == 3:
  122. if im.shape[2] not in [3, 4]:
  123. raise ValueError('This array can not represent an image.')
  124. else:
  125. raise ValueError('This array can not represent an image.')
  126. else:
  127. raise ValueError('Invalid image type: ' + str(type(im)))
  128. # Done
  129. return images2
  130. ## Base functions and classes
  131. class BitArray:
  132. """Dynamic array of bits that automatically resizes
  133. with factors of two.
  134. Append bits using .Append() or +=
  135. You can reverse bits using .Reverse()
  136. """
  137. def __init__(self, initvalue=None):
  138. self.data = np.zeros((16,), dtype=np.uint8)
  139. self._len = 0
  140. if initvalue is not None:
  141. self.Append(initvalue)
  142. def __len__(self):
  143. return self._len # self.data.shape[0]
  144. def __repr__(self):
  145. fn = getattr(self.data[:self._len], "tobytes", getattr(self.data[:self._len], "tostring"))
  146. return fn().decode('ascii')
  147. def _checkSize(self):
  148. # check length... grow if necessary
  149. arraylen = self.data.shape[0]
  150. if self._len >= arraylen:
  151. tmp = np.zeros((arraylen*2,), dtype=np.uint8)
  152. tmp[:self._len] = self.data[:self._len]
  153. self.data = tmp
  154. def __add__(self, value):
  155. self.Append(value)
  156. return self
  157. def Append(self, bits):
  158. # check input
  159. if isinstance(bits, BitArray):
  160. bits = str(bits)
  161. if isinstance(bits, int):
  162. bits = str(bits)
  163. if not isinstance(bits, string_types):
  164. raise ValueError("Append bits as strings or integers!")
  165. # add bits
  166. for bit in bits:
  167. self.data[self._len] = ord(bit)
  168. self._len += 1
  169. self._checkSize()
  170. def Reverse(self):
  171. """ In-place reverse. """
  172. tmp = self.data[:self._len].copy()
  173. self.data[:self._len] = np.flipud(tmp)
  174. def ToBytes(self):
  175. """ Convert to bytes. If necessary,
  176. zeros are padded to the end (right side).
  177. """
  178. bits = str(self)
  179. # determine number of bytes
  180. nbytes = 0
  181. while nbytes * 8 < len(bits):
  182. nbytes += 1
  183. # pad
  184. bits = bits.ljust(nbytes * 8, '0')
  185. # go from bits to bytes
  186. bb = binary_type()
  187. for i in range(nbytes):
  188. tmp = int(bits[i * 8: (i + 1) * 8], 2)
  189. bb += intToUint8(tmp)
  190. # done
  191. return bb
  192. if PY3:
  193. def intToUint32(i):
  194. return int(i).to_bytes(4, 'little')
  195. def intToUint16(i):
  196. return int(i).to_bytes(2, 'little')
  197. def intToUint8(i):
  198. return int(i).to_bytes(1, 'little')
  199. else:
  200. def intToUint32(i):
  201. number = int(i)
  202. n1, n2, n3, n4 = 1, 256, 256 * 256, 256 * 256 * 256
  203. b4, number = number // n4, number % n4
  204. b3, number = number // n3, number % n3
  205. b2, number = number // n2, number % n2
  206. b1 = number
  207. return chr(b1) + chr(b2) + chr(b3) + chr(b4)
  208. def intToUint16(i):
  209. i = int(i)
  210. # divide in two parts (bytes)
  211. i1 = i % 256
  212. i2 = int(i // 256)
  213. # make string (little endian)
  214. return chr(i1) + chr(i2)
  215. def intToUint8(i):
  216. return chr(int(i))
  217. def intToBits(i, n=None):
  218. """ convert int to a string of bits (0's and 1's in a string),
  219. pad to n elements. Convert back using int(ss,2). """
  220. ii = i
  221. # make bits
  222. bb = BitArray()
  223. while ii > 0:
  224. bb += str(ii % 2)
  225. ii = ii >> 1
  226. bb.Reverse()
  227. # justify
  228. if n is not None:
  229. if len(bb) > n:
  230. raise ValueError("intToBits fail: len larger than padlength.")
  231. bb = str(bb).rjust(n, '0')
  232. # done
  233. return BitArray(bb)
  234. def bitsToInt(bb, n=8):
  235. # Init
  236. value = ''
  237. # Get value in bits
  238. for i in range(len(bb)):
  239. b = bb[i:i+1]
  240. tmp = bin(ord(b))[2:]
  241. #value += tmp.rjust(8,'0')
  242. value = tmp.rjust(8, '0') + value
  243. # Make decimal
  244. return(int(value[:n], 2))
  245. def getTypeAndLen(bb):
  246. """ bb should be 6 bytes at least
  247. Return (type, length, length_of_full_tag)
  248. """
  249. # Init
  250. value = ''
  251. # Get first 16 bits
  252. for i in range(2):
  253. b = bb[i:i + 1]
  254. tmp = bin(ord(b))[2:]
  255. #value += tmp.rjust(8,'0')
  256. value = tmp.rjust(8, '0') + value
  257. # Get type and length
  258. type = int(value[:10], 2)
  259. L = int(value[10:], 2)
  260. L2 = L + 2
  261. # Long tag header?
  262. if L == 63: # '111111'
  263. value = ''
  264. for i in range(2, 6):
  265. b = bb[i:i + 1] # becomes a single-byte bytes() on both PY3 and PY2
  266. tmp = bin(ord(b))[2:]
  267. #value += tmp.rjust(8,'0')
  268. value = tmp.rjust(8, '0') + value
  269. L = int(value, 2)
  270. L2 = L + 6
  271. # Done
  272. return type, L, L2
  273. def signedIntToBits(i, n=None):
  274. """ convert signed int to a string of bits (0's and 1's in a string),
  275. pad to n elements. Negative numbers are stored in 2's complement bit
  276. patterns, thus positive numbers always start with a 0.
  277. """
  278. # negative number?
  279. ii = i
  280. if i < 0:
  281. # A negative number, -n, is represented as the bitwise opposite of
  282. ii = abs(ii) - 1 # the positive-zero number n-1.
  283. # make bits
  284. bb = BitArray()
  285. while ii > 0:
  286. bb += str(ii % 2)
  287. ii = ii >> 1
  288. bb.Reverse()
  289. # justify
  290. bb = '0' + str(bb) # always need the sign bit in front
  291. if n is not None:
  292. if len(bb) > n:
  293. raise ValueError("signedIntToBits fail: len larger than padlength.")
  294. bb = bb.rjust(n, '0')
  295. # was it negative? (then opposite bits)
  296. if i < 0:
  297. bb = bb.replace('0', 'x').replace('1', '0').replace('x', '1')
  298. # done
  299. return BitArray(bb)
  300. def twitsToBits(arr):
  301. """ Given a few (signed) numbers, store them
  302. as compactly as possible in the wat specifief by the swf format.
  303. The numbers are multiplied by 20, assuming they
  304. are twits.
  305. Can be used to make the RECT record.
  306. """
  307. # first determine length using non justified bit strings
  308. maxlen = 1
  309. for i in arr:
  310. tmp = len(signedIntToBits(i*20))
  311. if tmp > maxlen:
  312. maxlen = tmp
  313. # build array
  314. bits = intToBits(maxlen, 5)
  315. for i in arr:
  316. bits += signedIntToBits(i * 20, maxlen)
  317. return bits
  318. def floatsToBits(arr):
  319. """ Given a few (signed) numbers, convert them to bits,
  320. stored as FB (float bit values). We always use 16.16.
  321. Negative numbers are not (yet) possible, because I don't
  322. know how the're implemented (ambiguity).
  323. """
  324. bits = intToBits(31, 5) # 32 does not fit in 5 bits!
  325. for i in arr:
  326. if i < 0:
  327. raise ValueError("Dit not implement negative floats!")
  328. i1 = int(i)
  329. i2 = i - i1
  330. bits += intToBits(i1, 15)
  331. bits += intToBits(i2 * 2 ** 16, 16)
  332. return bits
  333. def _readFrom(fp, n):
  334. bb = binary_type()
  335. try:
  336. while len(bb) < n:
  337. tmp = fp.read(n-len(bb))
  338. bb += tmp
  339. if not tmp:
  340. break
  341. except EOFError:
  342. pass
  343. return bb
  344. ## Base Tag
  345. class Tag:
  346. def __init__(self):
  347. self.bytes = binary_type()
  348. self.tagtype = -1
  349. def ProcessTag(self):
  350. """ Implement this to create the tag. """
  351. raise NotImplementedError()
  352. def GetTag(self):
  353. """ Calls processTag and attaches the header. """
  354. self.ProcessTag()
  355. # tag to binary
  356. bits = intToBits(self.tagtype, 10)
  357. # complete header uint16 thing
  358. bits += '1' * 6 # = 63 = 0x3f
  359. # make uint16
  360. bb = intToUint16(int(str(bits), 2))
  361. # now add 32bit length descriptor
  362. bb += intToUint32(len(self.bytes))
  363. # done, attach and return
  364. bb += self.bytes
  365. return bb
  366. def MakeRectRecord(self, xmin, xmax, ymin, ymax):
  367. """ Simply uses makeCompactArray to produce
  368. a RECT Record. """
  369. return twitsToBits([xmin, xmax, ymin, ymax])
  370. def MakeMatrixRecord(self, scale_xy=None, rot_xy=None, trans_xy=None):
  371. # empty matrix?
  372. if scale_xy is None and rot_xy is None and trans_xy is None:
  373. return "0"*8
  374. # init
  375. bits = BitArray()
  376. # scale
  377. if scale_xy:
  378. bits += '1'
  379. bits += floatsToBits([scale_xy[0], scale_xy[1]])
  380. else:
  381. bits += '0'
  382. # rotation
  383. if rot_xy:
  384. bits += '1'
  385. bits += floatsToBits([rot_xy[0], rot_xy[1]])
  386. else:
  387. bits += '0'
  388. # translation (no flag here)
  389. if trans_xy:
  390. bits += twitsToBits([trans_xy[0], trans_xy[1]])
  391. else:
  392. bits += twitsToBits([0, 0])
  393. # done
  394. return bits
  395. ## Control tags
  396. class ControlTag(Tag):
  397. def __init__(self):
  398. Tag.__init__(self)
  399. class FileAttributesTag(ControlTag):
  400. def __init__(self):
  401. ControlTag.__init__(self)
  402. self.tagtype = 69
  403. def ProcessTag(self):
  404. self.bytes = '\x00'.encode('ascii') * (1+3)
  405. class ShowFrameTag(ControlTag):
  406. def __init__(self):
  407. ControlTag.__init__(self)
  408. self.tagtype = 1
  409. def ProcessTag(self):
  410. self.bytes = binary_type()
  411. class SetBackgroundTag(ControlTag):
  412. """ Set the color in 0-255, or 0-1 (if floats given). """
  413. def __init__(self, *rgb):
  414. self.tagtype = 9
  415. if len(rgb) == 1:
  416. rgb = rgb[0]
  417. self.rgb = rgb
  418. def ProcessTag(self):
  419. bb = binary_type()
  420. for i in range(3):
  421. clr = self.rgb[i]
  422. if isinstance(clr, float):
  423. clr = clr * 255
  424. bb += intToUint8(clr)
  425. self.bytes = bb
  426. class DoActionTag(Tag):
  427. def __init__(self, action='stop'):
  428. Tag.__init__(self)
  429. self.tagtype = 12
  430. self.actions = [action]
  431. def Append(self, action):
  432. self.actions.append(action)
  433. def ProcessTag(self):
  434. bb = binary_type()
  435. for action in self.actions:
  436. action = action.lower()
  437. if action == 'stop':
  438. bb += '\x07'.encode('ascii')
  439. elif action == 'play':
  440. bb += '\x06'.encode('ascii')
  441. else:
  442. print("warning, unknown action: %s" % action)
  443. bb += intToUint8(0)
  444. self.bytes = bb
  445. ## Definition tags
  446. class DefinitionTag(Tag):
  447. counter = 0 # to give automatically id's
  448. def __init__(self):
  449. Tag.__init__(self)
  450. DefinitionTag.counter += 1
  451. self.id = DefinitionTag.counter # id in dictionary
  452. class BitmapTag(DefinitionTag):
  453. def __init__(self, im):
  454. DefinitionTag.__init__(self)
  455. self.tagtype = 36 # DefineBitsLossless2
  456. # convert image (note that format is ARGB)
  457. # even a grayscale image is stored in ARGB, nevertheless,
  458. # the fabilous deflate compression will make it that not much
  459. # more data is required for storing (25% or so, and less than 10%
  460. # when storing RGB as ARGB).
  461. if len(im.shape) == 3:
  462. if im.shape[2] in [3, 4]:
  463. tmp = np.ones((im.shape[0], im.shape[1], 4),
  464. dtype=np.uint8) * 255
  465. for i in range(3):
  466. tmp[:, :, i + 1] = im[:, :, i]
  467. if im.shape[2] == 4:
  468. tmp[:, :, 0] = im[:, :, 3] # swap channel where alpha is in
  469. else:
  470. raise ValueError("Invalid shape to be an image.")
  471. elif len(im.shape) == 2:
  472. tmp = np.ones((im.shape[0], im.shape[1], 4), dtype=np.uint8)*255
  473. for i in range(3):
  474. tmp[:, :, i + 1] = im[:, :]
  475. else:
  476. raise ValueError("Invalid shape to be an image.")
  477. # we changed the image to uint8 4 channels.
  478. # now compress!
  479. self._data = zlib.compress(tmp.tostring(), zlib.DEFLATED)
  480. self.imshape = im.shape
  481. def ProcessTag(self):
  482. # build tag
  483. bb = binary_type()
  484. bb += intToUint16(self.id) # CharacterID
  485. bb += intToUint8(5) # BitmapFormat
  486. bb += intToUint16(self.imshape[1]) # BitmapWidth
  487. bb += intToUint16(self.imshape[0]) # BitmapHeight
  488. bb += self._data # ZlibBitmapData
  489. self.bytes = bb
  490. class PlaceObjectTag(ControlTag):
  491. def __init__(self, depth, idToPlace=None, xy=(0, 0), move=False):
  492. ControlTag.__init__(self)
  493. self.tagtype = 26
  494. self.depth = depth
  495. self.idToPlace = idToPlace
  496. self.xy = xy
  497. self.move = move
  498. def ProcessTag(self):
  499. # retrieve stuff
  500. depth = self.depth
  501. xy = self.xy
  502. id = self.idToPlace
  503. # build PlaceObject2
  504. bb = binary_type()
  505. if self.move:
  506. bb += '\x07'.encode('ascii')
  507. else:
  508. bb += '\x06'.encode('ascii') # (8 bit flags): 4:matrix, 2:character, 1:move
  509. bb += intToUint16(depth) # Depth
  510. bb += intToUint16(id) # character id
  511. bb += self.MakeMatrixRecord(trans_xy=xy).ToBytes() # MATRIX record
  512. self.bytes = bb
  513. class ShapeTag(DefinitionTag):
  514. def __init__(self, bitmapId, xy, wh):
  515. DefinitionTag.__init__(self)
  516. self.tagtype = 2
  517. self.bitmapId = bitmapId
  518. self.xy = xy
  519. self.wh = wh
  520. def ProcessTag(self):
  521. """ Returns a defineshape tag. with a bitmap fill """
  522. bb = binary_type()
  523. bb += intToUint16(self.id)
  524. xy, wh = self.xy, self.wh
  525. tmp = self.MakeRectRecord(xy[0], wh[0], xy[1], wh[1]) # ShapeBounds
  526. bb += tmp.ToBytes()
  527. # make SHAPEWITHSTYLE structure
  528. # first entry: FILLSTYLEARRAY with in it a single fill style
  529. bb += intToUint8(1) # FillStyleCount
  530. bb += '\x41'.encode('ascii') # FillStyleType (0x41 or 0x43, latter is non-smoothed)
  531. bb += intToUint16(self.bitmapId) # BitmapId
  532. #bb += '\x00' # BitmapMatrix (empty matrix with leftover bits filled)
  533. bb += self.MakeMatrixRecord(scale_xy=(20, 20)).ToBytes()
  534. # # first entry: FILLSTYLEARRAY with in it a single fill style
  535. # bb += intToUint8(1) # FillStyleCount
  536. # bb += '\x00' # solid fill
  537. # bb += '\x00\x00\xff' # color
  538. # second entry: LINESTYLEARRAY with a single line style
  539. bb += intToUint8(0) # LineStyleCount
  540. #bb += intToUint16(0*20) # Width
  541. #bb += '\x00\xff\x00' # Color
  542. # third and fourth entry: NumFillBits and NumLineBits (4 bits each)
  543. # I each give them four bits, so 16 styles possible.
  544. bb += '\x44'.encode('ascii')
  545. self.bytes = bb
  546. # last entries: SHAPERECORDs ... (individual shape records not aligned)
  547. # STYLECHANGERECORD
  548. bits = BitArray()
  549. bits += self.MakeStyleChangeRecord(0, 1, moveTo=(self.wh[0],
  550. self.wh[1]))
  551. # STRAIGHTEDGERECORD 4x
  552. bits += self.MakeStraightEdgeRecord(-self.wh[0], 0)
  553. bits += self.MakeStraightEdgeRecord(0, -self.wh[1])
  554. bits += self.MakeStraightEdgeRecord(self.wh[0], 0)
  555. bits += self.MakeStraightEdgeRecord(0, self.wh[1])
  556. # ENDSHAPRECORD
  557. bits += self.MakeEndShapeRecord()
  558. self.bytes += bits.ToBytes()
  559. # done
  560. #self.bytes = bb
  561. def MakeStyleChangeRecord(self, lineStyle=None, fillStyle=None,
  562. moveTo=None):
  563. # first 6 flags
  564. # Note that we use FillStyle1. If we don't flash (at least 8) does not
  565. # recognize the frames properly when importing to library.
  566. bits = BitArray()
  567. bits += '0' # TypeFlag (not an edge record)
  568. bits += '0' # StateNewStyles (only for DefineShape2 and Defineshape3)
  569. if lineStyle:
  570. bits += '1' # StateLineStyle
  571. else:
  572. bits += '0'
  573. if fillStyle:
  574. bits += '1' # StateFillStyle1
  575. else:
  576. bits += '0'
  577. bits += '0' # StateFillStyle0
  578. if moveTo:
  579. bits += '1' # StateMoveTo
  580. else:
  581. bits += '0'
  582. # give information
  583. # todo: nbits for fillStyle and lineStyle is hard coded.
  584. if moveTo:
  585. bits += twitsToBits([moveTo[0], moveTo[1]])
  586. if fillStyle:
  587. bits += intToBits(fillStyle, 4)
  588. if lineStyle:
  589. bits += intToBits(lineStyle, 4)
  590. return bits
  591. #return bitsToBytes(bits)
  592. def MakeStraightEdgeRecord(self, *dxdy):
  593. if len(dxdy) == 1:
  594. dxdy = dxdy[0]
  595. # determine required number of bits
  596. xbits = signedIntToBits(dxdy[0] * 20)
  597. ybits = signedIntToBits(dxdy[1] * 20)
  598. nbits = max([len(xbits), len(ybits)])
  599. bits = BitArray()
  600. bits += '11' # TypeFlag and StraightFlag
  601. bits += intToBits(nbits-2, 4)
  602. bits += '1' # GeneralLineFlag
  603. bits += signedIntToBits(dxdy[0] * 20, nbits)
  604. bits += signedIntToBits(dxdy[1] * 20, nbits)
  605. # note: I do not make use of vertical/horizontal only lines...
  606. return bits
  607. #return bitsToBytes(bits)
  608. def MakeEndShapeRecord(self):
  609. bits = BitArray()
  610. bits += "0" # TypeFlag: no edge
  611. bits += "0"*5 # EndOfShape
  612. return bits
  613. #return bitsToBytes(bits)
  614. ## Last few functions
  615. def buildFile(fp, taglist, nframes=1, framesize=(500, 500), fps=10, version=8):
  616. """ Give the given file (as bytes) a header. """
  617. # compose header
  618. bb = binary_type()
  619. bb += 'F'.encode('ascii') # uncompressed
  620. bb += 'WS'.encode('ascii') # signature bytes
  621. bb += intToUint8(version) # version
  622. bb += '0000'.encode('ascii') # FileLength (leave open for now)
  623. bb += Tag().MakeRectRecord(0, framesize[0], 0, framesize[1]).ToBytes()
  624. bb += intToUint8(0) + intToUint8(fps) # FrameRate
  625. bb += intToUint16(nframes)
  626. fp.write(bb)
  627. # produce all tags
  628. for tag in taglist:
  629. fp.write(tag.GetTag())
  630. # finish with end tag
  631. fp.write('\x00\x00'.encode('ascii'))
  632. # set size
  633. sze = fp.tell()
  634. fp.seek(4)
  635. fp.write(intToUint32(sze))
  636. def writeSwf(filename, images, duration=0.1, repeat=True):
  637. """Write an swf-file from the specified images. If repeat is False,
  638. the movie is finished with a stop action. Duration may also
  639. be a list with durations for each frame (note that the duration
  640. for each frame is always an integer amount of the minimum duration.)
  641. Images should be a list consisting of PIL images or numpy arrays.
  642. The latter should be between 0 and 255 for integer types, and
  643. between 0 and 1 for float types.
  644. """
  645. # Check Numpy
  646. if np is None:
  647. raise RuntimeError("Need Numpy to write an SWF file.")
  648. # Check images (make all Numpy)
  649. images2 = []
  650. images = checkImages(images)
  651. if not images:
  652. raise ValueError("Image list is empty!")
  653. for im in images:
  654. if PIL and isinstance(im, PIL.Image.Image):
  655. if im.mode == 'P':
  656. im = im.convert()
  657. im = np.asarray(im)
  658. if len(im.shape) == 0:
  659. raise MemoryError("Too little memory to convert PIL image to array")
  660. images2.append(im)
  661. # Init
  662. taglist = [FileAttributesTag(), SetBackgroundTag(0, 0, 0)]
  663. # Check duration
  664. if hasattr(duration, '__len__'):
  665. if len(duration) == len(images2):
  666. duration = [d for d in duration]
  667. else:
  668. raise ValueError("len(duration) doesn't match amount of images.")
  669. else:
  670. duration = [duration for im in images2]
  671. # Build delays list
  672. minDuration = float(min(duration))
  673. delays = [round(d/minDuration) for d in duration]
  674. delays = [max(1, int(d)) for d in delays]
  675. # Get FPS
  676. fps = 1.0/minDuration
  677. # Produce series of tags for each image
  678. nframes = 0
  679. for im in images2:
  680. bm = BitmapTag(im)
  681. wh = (im.shape[1], im.shape[0])
  682. sh = ShapeTag(bm.id, (0, 0), wh)
  683. po = PlaceObjectTag(1, sh.id, move=nframes > 0)
  684. taglist.extend([bm, sh, po])
  685. for i in range(delays[nframes]):
  686. taglist.append(ShowFrameTag())
  687. nframes += 1
  688. if not repeat:
  689. taglist.append(DoActionTag('stop'))
  690. # Build file
  691. fp = open(filename, 'wb')
  692. try:
  693. buildFile(fp, taglist, nframes=nframes, framesize=wh, fps=fps)
  694. except Exception:
  695. raise
  696. finally:
  697. fp.close()
  698. def _readPixels(bb, i, tagType, L1):
  699. """ With pf's seed after the recordheader, reads the pixeldata.
  700. """
  701. # Check Numpy
  702. if np is None:
  703. raise RuntimeError("Need Numpy to read an SWF file.")
  704. # Get info
  705. charId = bb[i:i + 2]
  706. i += 2
  707. format = ord(bb[i:i + 1])
  708. i += 1
  709. width = bitsToInt(bb[i:i + 2], 16)
  710. i += 2
  711. height = bitsToInt(bb[i:i + 2], 16)
  712. i += 2
  713. # If we can, get pixeldata and make nunmpy array
  714. if format != 5:
  715. print("Can only read 24bit or 32bit RGB(A) lossless images.")
  716. else:
  717. # Read byte data
  718. offset = 2 + 1 + 2 + 2 # all the info bits
  719. bb2 = bb[i:i+(L1-offset)]
  720. # Decompress and make numpy array
  721. data = zlib.decompress(bb2)
  722. a = np.frombuffer(data, dtype=np.uint8)
  723. # Set shape
  724. if tagType == 20:
  725. # DefineBitsLossless - RGB data
  726. try:
  727. a.shape = height, width, 3
  728. except Exception:
  729. # Byte align stuff might cause troubles
  730. print("Cannot read image due to byte alignment")
  731. if tagType == 36:
  732. # DefineBitsLossless2 - ARGB data
  733. a.shape = height, width, 4
  734. # Swap alpha channel to make RGBA
  735. b = a
  736. a = np.zeros_like(a)
  737. a[:, :, 0] = b[:, :, 1]
  738. a[:, :, 1] = b[:, :, 2]
  739. a[:, :, 2] = b[:, :, 3]
  740. a[:, :, 3] = b[:, :, 0]
  741. return a
  742. def readSwf(filename, asNumpy=True):
  743. """Read all images from an SWF (shockwave flash) file. Returns a list
  744. of numpy arrays, or, if asNumpy is false, a list if PIL images.
  745. Limitation: only read the PNG encoded images (not the JPG encoded ones).
  746. """
  747. # Check whether it exists
  748. if not os.path.isfile(filename):
  749. raise IOError('File not found: '+str(filename))
  750. # Check PIL
  751. if (not asNumpy) and (PIL is None):
  752. raise RuntimeError("Need PIL to return as PIL images.")
  753. # Check Numpy
  754. if np is None:
  755. raise RuntimeError("Need Numpy to read SWF files.")
  756. # Init images
  757. images = []
  758. # Open file and read all
  759. fp = open(filename, 'rb')
  760. bb = fp.read()
  761. try:
  762. # Check opening tag
  763. tmp = bb[0:3].decode('ascii', 'ignore')
  764. if tmp.upper() == 'FWS':
  765. pass # ok
  766. elif tmp.upper() == 'CWS':
  767. # Decompress movie
  768. bb = bb[:8] + zlib.decompress(bb[8:])
  769. else:
  770. raise IOError('Not a valid SWF file: ' + str(filename))
  771. # Set filepointer at first tag (skipping framesize RECT and two uin16's
  772. i = 8
  773. nbits = bitsToInt(bb[i: i + 1], 5) # skip FrameSize
  774. nbits = 5 + nbits * 4
  775. Lrect = nbits / 8.0
  776. if Lrect % 1:
  777. Lrect += 1
  778. Lrect = int(Lrect)
  779. i += Lrect+4
  780. # Iterate over the tags
  781. counter = 0
  782. while True:
  783. counter += 1
  784. # Get tag header
  785. head = bb[i:i+6]
  786. if not head:
  787. break # Done (we missed end tag)
  788. # Determine type and length
  789. T, L1, L2 = getTypeAndLen(head)
  790. if not L2:
  791. print('Invalid tag length, could not proceed')
  792. break
  793. #print(T, L2)
  794. # Read image if we can
  795. if T in [20, 36]:
  796. im = _readPixels(bb, i+6, T, L1)
  797. if im is not None:
  798. images.append(im)
  799. elif T in [6, 21, 35, 90]:
  800. print('Ignoring JPEG image: cannot read JPEG.')
  801. else:
  802. pass # Not an image tag
  803. # Detect end tag
  804. if T == 0:
  805. break
  806. # Next tag!
  807. i += L2
  808. finally:
  809. fp.close()
  810. # Convert to normal PIL images if needed
  811. if not asNumpy:
  812. images2 = images
  813. images = []
  814. for im in images2:
  815. images.append(PIL.Image.fromarray(im))
  816. # Done
  817. return images