1. --!parent
  2.  
  3. -- ************************************************************************
  4. -- APNG Encoder - version based on strings (not byteArray), for Director 10 or older
  5. --
  6. -- @author Valentin Schmidt
  7. -- @version 0.6
  8. -- @requires xtra "ImgXtra", xtra "fileIO"
  9. --           Optional function optimize_apng() requires xtra "Shell" and apngopt.exe
  10. -- ************************************************************************
  11.  
  12. property _ix
  13. property _data
  14. property _color_type
  15. property _sequence_number
  16. property _num_plays
  17. property _num_frames
  18. property _palette_colors
  19. property _tranparency
  20.  
  21. -- constants for dispose_op parameter
  22. property DISPOSE_OP_NONE
  23. property DISPOSE_OP_BACKGROUND
  24. property DISPOSE_OP_PREVIOUS
  25.  
  26. -- constants for blend_op parameter
  27. property BLEND_OP_SOURCE
  28. property BLEND_OP_OVER
  29.  
  30. -- filter constants
  31. property FILTER_NONE
  32. property FILTER_SUB
  33. property FILTER_UP
  34.  
  35. ----------------------------------------
  36. -- @constructor
  37. ----------------------------------------
  38. on new (me)
  39.     me._ix = xtra("ImgXtra").new()
  40.  
  41.     -- no disposal is done on this frame before rendering the next; the contents
  42.     -- of the output buffer are left as is.
  43.     me.DISPOSE_OP_NONE = 0
  44.  
  45.     -- the frame's region of the output buffer is to be cleared to fully
  46.     -- transparent black before rendering the next frame.
  47.     me.DISPOSE_OP_BACKGROUND = 1
  48.  
  49.     -- the frame's region of the output buffer is to be reverted to the previous
  50.     -- contents before rendering the next frame.
  51.     me.DISPOSE_OP_PREVIOUS = 2
  52.  
  53.     -- all color components of the frame, including alpha, overwrite the current
  54.     -- contents of the frame's output buffer region
  55.     me.BLEND_OP_SOURCE = 0
  56.  
  57.     -- the frame should be composited onto the output buffer based on its alpha,
  58.     -- using a simple OVER operation as described in the "Alpha Channel Processing"
  59.     -- section of the PNG specification
  60.     me.BLEND_OP_OVER = 1
  61.  
  62.     -- the scanline is transmitted unmodified
  63.     me.FILTER_NONE = 0
  64.  
  65.     -- transmits the difference between each byte and the value of the
  66.     -- corresponding byte of the prior pixel
  67.     me.FILTER_SUB = 1
  68.  
  69.     -- transmits the difference between each byte and the value of the
  70.     -- corresponding byte of the pixel above
  71.     me.FILTER_UP = 2
  72.  
  73.     -- NOTE: other filters like "Average" and "Paeth" are currently not supported
  74.     -- by this library
  75.  
  76.     return me
  77. end
  78.  
  79. ----------------------------------------
  80. -- Starts a new APNG
  81. -- @param {integer} [num_plays=0] - how often to loop, 0 means endless looping (default)
  82. -- @param {list} [palette_colors] - if specified, all frame images will be mapped to
  83. --        the colors in this list
  84. -- @param {integer|list} [transparency_index] - index of color in palette_colors that
  85. --        will be transparent in the final APNG file; for 8-bit images transparency_index
  86. --        can also be a list of indexes
  87. ----------------------------------------
  88. on init (me, num_plays, palette_colors, transparency_index)
  89.     if voidP(num_plays) then num_plays = 0
  90.     me._num_plays = num_plays
  91.     me._num_frames = 0
  92.     me._sequence_number = 0
  93.     me._palette_colors = palette_colors
  94.     me._tranparency = transparency_index
  95.  
  96.     -- write PNG signature
  97.     me._data = numtochar(137)&"PNG"
  98.     me._writeBytes(13,10,26,10)
  99. end
  100.  
  101. ----------------------------------------
  102. -- Adds new frame to APNG
  103. -- @param {image} frame_image - the frame as Lingo image object
  104. -- @param {integer} ms - duration of the frame in milliseconds
  105. -- @param {integer} [x=0] - x-offset of the image
  106. -- @param {integer} [y=0] - y-offset of the image
  107. -- @param {integer} [dispose_op=0] - how frame is disposed, see comments below
  108. -- @param {integer} [blend_op=0] - how frame is blended, see comments below
  109. -- @param {integer} [filter=0] - PNG filter, see comments above
  110. -- @return {bool} success
  111. ----------------------------------------
  112. on addFrame (me, frame_image, ms, x, y, dispose_op, blend_op, filter)
  113.     if voidP(x) then x = 0
  114.     if voidP(y) then y = 0
  115.     if voidP(dispose_op) then dispose_op = 0
  116.     if voidP(blend_op) then blend_op = 0
  117.     if voidP(filter) then filter = 0
  118.  
  119.     me._num_frames = me._num_frames + 1
  120.  
  121.     if not voidP(me._palette_colors) then
  122.         -- quantize to reduced color palette
  123.         props = [:]
  124.         props["image"] = frame_image
  125.         props["reserved_colors"] = me._palette_colors
  126.         frame_image = me._ix.ix_quantizeImage(props)
  127.     end if
  128.  
  129.     -- the "master" frame
  130.     if me._num_frames=1 then
  131.         x = 0
  132.         y = 0
  133.         case (frame_image.depth) of
  134.             24, 32:
  135.                 if frame_image.useAlpha then
  136.                     me._color_type = 6 -- RGB with alpha
  137.                 else
  138.                     me._color_type = 2 -- RGB
  139.                 end if
  140.             8:
  141.                 if frame_image.paletteRef=#grayscale then
  142.                     me._color_type = 0 -- GS
  143.                 else
  144.                     me._color_type = 3 -- Palette
  145.                 end if
  146.             otherwise:
  147.                 return FALSE -- 1, 4, 16-bit unsupported!
  148.         end case
  149.  
  150.         -- add IHDR chunk
  151.         IHDR = ""
  152.         put me._int32ToStr(frame_image.width) after IHDR
  153.         put me._int32ToStr(frame_image.height) after IHDR
  154.         put numtochar(8)&numtochar(me._color_type)&numtochar(0)&numtochar(0)&numtochar(0) after IHDR
  155.         me._writeChunk("IHDR", IHDR)
  156.  
  157.         -- add acTL (animation control chunk) => updated later
  158.         put me._int32ToStr(8)&"acTL"&me._int32ToStr(0)&me._int32ToStr(me._num_plays)&me._int32ToStr(0) after _data
  159.     end if
  160.  
  161.     -- add fcTL (frame control chunk)
  162.     fcTL = ""
  163.     put me._int32ToStr(me._sequence_number) after fcTL
  164.     me._sequence_number = me._sequence_number + 1
  165.     put me._int32ToStr(frame_image.width) after fcTL
  166.     put me._int32ToStr(frame_image.height) after fcTL
  167.     put me._int32ToStr(x) after fcTL
  168.     put me._int32ToStr(y) after fcTL
  169.  
  170.     -- The delay_num and delay_den parameters together specify a fraction
  171.     -- indicating the time to display the current frame, in seconds. If the denominator
  172.     -- is 0, it is to be treated as if it were 100 (that is, `delay_num` then specifies
  173.     -- 1/100ths of a second). If the the value of the numerator is 0 the decoder should
  174.     -- render the next frame as quickly as possible, though viewers may impose a
  175.     -- reasonable lower bound.
  176.     -- To keep things simple, we set delay_den to 1000, so delay_num is duration in ms.
  177.     put me._int16ToStr(ms) after fcTL -- delay_num
  178.     put me._int16ToStr(1000) after fcTL -- delay_den
  179.  
  180.     put numtochar(dispose_op) after fcTL
  181.     put numtochar(blend_op) after fcTL
  182.  
  183.     me._writeChunk("fcTL", fcTL)
  184.  
  185.     -- build IDAT or fdAT chunk
  186.     case (me._color_type) of
  187.         2: -- RGB
  188.             chunk = me._ix.ix_imageToRaw(["image":frame_image, "pattern":"RGB", "png_filter":filter])
  189.         6: -- RGBA
  190.             chunk = me._ix.ix_imageToRaw(["image":frame_image, "pattern":"RGBA", "png_filter":filter])
  191.         0, 3: -- grayscale/palette
  192.             chunk = me._ix.ix_imageToRaw(["image":frame_image, "png_filter":filter])
  193.     end case
  194.  
  195.     -- compress the IDAT/fdAT chunk
  196.     chunk = me._ix.ix_zlibCompress(["data":chunk])
  197.  
  198.     if me._sequence_number>1 then
  199.         put me._int32ToStr(me._sequence_number) before chunk
  200.         me._sequence_number = me._sequence_number + 1
  201.         me._writeChunk("fdAT", chunk)
  202.     else
  203.  
  204.         -- add PLTE chunk (custom palette)
  205.         if me._color_type=3 then
  206.             PLTE = ""
  207.             repeat with col in me._palette_colors
  208.                 put numtochar(col.red)&numtochar(col.green)&numtochar(col.blue) after PLTE
  209.             end repeat
  210.             repeat with i = me._palette_colors.count+1 to 256
  211.                 put numtochar(0)&numtochar(0)&numtochar(0) after PLTE
  212.             end repeat
  213.             me._writeChunk("PLTE", PLTE)
  214.         end if
  215.  
  216.         -- add tRNS chunk (transparent color index)
  217.         if not voidP(me._tranparency) then
  218.             case (me._color_type) of
  219.                 0: -- GS
  220.                     tRNS = ""
  221.                     put numtochar(0) & numtochar(me._tranparency) after tRNS
  222.                     me._writeChunk("tRNS", tRNS)
  223.  
  224.                 2: -- RGB
  225.                     tRNS = ""
  226.                     put numtochar(0) & numtochar(me._tranparency.red) after tRNS
  227.                     put numtochar(0) & numtochar(me._tranparency.green) after tRNS
  228.                     put numtochar(0) & numtochar(me._tranparency.blue) after tRNS
  229.                     me._writeChunk("tRNS", tRNS)
  230.  
  231.                 3: -- Palette
  232.                     tRNS = ""
  233.                     if integerP(me._tranparency) then
  234.                         repeat with i = 1 to 256
  235.                             if i=me._tranparency then
  236.                                 put numtochar(0) after tRNS
  237.                             else
  238.                                 put numtochar(255) after tRNS
  239.                             end if
  240.                         end repeat
  241.                     else if listP(me._tranparency) then
  242.                         repeat with t in me._tranparency
  243.                             put numtochar(t) after tRNS
  244.                         end repeat
  245.                         repeat with i = me._tranparency.count+1 to 256
  246.                             put numtochar(255) after tRNS
  247.                         end repeat
  248.                     end if
  249.                     me._writeChunk("tRNS", tRNS)
  250.             end case
  251.         end if
  252.  
  253.         me._writeChunk("IDAT", chunk)
  254.     end if
  255.  
  256.     return TRUE
  257. end
  258.  
  259. ----------------------------------------
  260. -- Returns APNG as binary string
  261. -- Note: you can either call getData() or writeFile(), but not both
  262. -- @return {string}
  263. ----------------------------------------
  264. on getData (me)
  265.  
  266.     -- add IEND chunk
  267.     put numtochar(0)&numtochar(0)&numtochar(0)&numtochar(0)&"IEND"&\
  268.     numtochar(174)&numtochar(66)&numtochar(96)&numtochar(130) after _data
  269.  
  270.     actl_pos = 33
  271.  
  272.     -- update num_frames in acTL chunk
  273.     put me._int32ToStr(me._num_frames) into char (actl_pos+9) to (actl_pos+12) of _data
  274.  
  275.     -- update CRC of acTL chunk
  276.     crc_data = _data.char[actl_pos+5..actl_pos+16]
  277.     crc = me._ix.ix_xCrc32String(["data":crc_data])
  278.     put crc into char (actl_pos+17) to (actl_pos+20) of _data
  279.  
  280.     return me._data
  281. end
  282.  
  283. ----------------------------------------
  284. -- Saves APNG as file
  285. -- Note: you can either call getData() or writeFile(), but not both
  286. -- @param {string} png_file
  287. -- @return {bool} success
  288. ----------------------------------------
  289. on writeFile (me, png_file)
  290.  
  291.     -- add IEND chunk
  292.     put numtochar(0)&numtochar(0)&numtochar(0)&numtochar(0)&"IEND"&\
  293.     numtochar(174)&numtochar(66)&numtochar(96)&numtochar(130) after _data
  294.  
  295.     actl_pos = 33
  296.  
  297.     -- update num_frames in acTL chunk
  298.     put me._int32ToStr(me._num_frames) into char (actl_pos+9) to (actl_pos+12) of _data
  299.  
  300.     -- update CRC of acTL chunk
  301.     crc_data = _data.char[actl_pos+5..actl_pos+16]
  302.     crc = me._ix.ix_xCrc32String(["data":crc_data])
  303.     put crc into char (actl_pos+17) to (actl_pos+20) of _data
  304.  
  305.     -- save to file
  306.     fp = xtra("fileIO").new()
  307.     fp.openFile(png_file, 2)
  308.     err = fp.status()
  309.     if not err then fp.delete()
  310.     else if (err and not (err = -37)) then return FALSE
  311.     fp.createFile(png_file)
  312.     if fp.status() then return FALSE
  313.     fp.openFile(png_file, 2)
  314.     if fp.status() then return FALSE
  315.     fp.writeString(me._data)
  316.     fp.closeFile()
  317.     return TRUE
  318. end
  319.  
  320. ----------------------------------------
  321. -- Utility function: pass a "typical" image for the animation; returns a palette as list of colors
  322. -- @param {image} master_image
  323. -- @param {integer} [palette_size=256]
  324. -- @return {list|FALSE}
  325. ----------------------------------------
  326. on findPalette (me, master_image, palette_size)
  327.     props = [:]
  328.     props["image"] = master_image
  329.     if integerP(palette_size) then props["palette_size"] = palette_size
  330.     img = me._ix.ix_quantizeImage(props)
  331.     if ilk(img)<>#image then return FALSE
  332.     palette_colors = me._ix.ix_getPaletteColors(["palette":img.paletteRef])
  333.     img.paletteRef.erase()
  334.     return palette_colors
  335. end
  336.  
  337. ----------------------------------------
  338. -- Utility function: optimizes APNG file using apngopt.exe
  339. -- Usage: ok = optimize_apng(_movie.path&"foo.png",\
  340. --     _movie.path&"foo_opt.png")
  341. --
  342. -- @requires Shell xtra, apngopt.exe
  343. -- @param {string} aPngFileIn
  344. -- @param {string} aPngFileOut
  345. -- @return {bool} success
  346. ----------------------------------------
  347. on optimize_apng (me, aPngFileIn, aPngFileOut)
  348.     sx = xtra("Shell").new()
  349.     props = [:]
  350.     props["show_cmd"] = 0
  351.     props["wait"] = 1
  352.     props["parameters"] = QUOTE & aPngFileIn & QUOTE && QUOTE & aPngFileOut & QUOTE
  353.     exit_code = sx.shell_execex(the moviePath & "bin\apngopt.exe", props)
  354.     return (exit_code=0)
  355. end
  356.  
  357. ----------------------------------------
  358. -- @private
  359. ----------------------------------------
  360. on _writeChunk (me, chunk_type, chunk_data)
  361.     put me._int32ToStr(chunk_data.length) after _data
  362.     put chunk_type before chunk_data
  363.     put chunk_data after _data
  364.     put me._ix.ix_xCrc32String(["data":chunk_data]) after _data
  365. end
  366.  
  367. ----------------------------------------
  368. -- @private
  369. ----------------------------------------
  370. on _writeBytes (me)
  371.     cnt = paramCount()
  372.     repeat with i = 2 to cnt
  373.         put numtochar(param(i)) after _data
  374.     end repeat
  375. end
  376.  
  377. ----------------------------------------
  378. -- @private
  379. ----------------------------------------
  380. on _int32ToStr (me, n)
  381.     return numtochar(bitAnd(n, 4278190080)/16777216)&\
  382.     numtochar(bitAnd(n, 16711680)/65536)&\
  383.     numtochar(bitAnd(n, 65280)/256)&\
  384.     numtochar(bitAnd(n, 255))
  385. end
  386.  
  387. ----------------------------------------
  388. -- @private
  389. ----------------------------------------
  390. on _int16ToStr (me, n)
  391.     return numtochar(bitAnd(n, 65280)/256)&\
  392.     numtochar(bitAnd(n, 255))
  393. end
  394.  
[raw code]