1. --!movie
  2. --!encoding=utf-8
  3.  
  4. --****************************************************************************
  5. -- Software: File Export Library for Bitmap, Sound and Flash members
  6. -- Version:  0.2
  7. -- Date:     2015-08-12
  8. -- Author:   Valentin Schmidt
  9. --
  10. -- Requirements/Dependencies:
  11. -- * Director 11.5+ (Win only?)
  12. -- * Xtra "FileIO"
  13. -- * Xtra "Crypto" (http://valentin.dasdeck.com/xtras/crypto_xtra/win/d11.5/)
  14. --
  15. -- ************************************************************************
  16.  
  17. ----------------------------------------
  18. -- Exports bitmap member. If member is compressed, the original JPG/PNG data is saved,
  19. -- otherwise the uncompressed bitmap is saved as BMP file.
  20. -- @param {string} tMemRef
  21. -- @param {string} tFolder
  22. -- @param {string} [tBaseName] - Optional, if omitted, memberName is used.
  23. -- @return {string|false} filetype ("jpg" or "png" or "bmp") or false
  24. ----------------------------------------
  25. on exportBitmapMember (tMemRef, tFolder, tBasename)
  26.   if tMemRef.type<>#bitmap then return false
  27.   tBytes = cx_member_to_bytes(tMemRef, true)
  28.   if ilk(tBytes)<>#bytearray then return false
  29.   if tBytes.length<29 then return false
  30.    
  31.   if voidP(tBaseName) then tBaseName = tMemRef.name
  32.   pd = the last char of _movie.path
  33.   if the last char of tFolder<>pd then put pd after tFolder
  34.  
  35.   formatLen = tBytes[25]
  36.   if formatLen<=18 then  
  37.     tBytes.position = 29
  38.     dataType = tBytes.readRawString(formatLen)
  39.   end if
  40.  
  41.   if dataType="kMoaCfFormat_JPEG" then
  42.     tFileType = "jpg"
  43.     tOffset = 85
  44.     tBytes.position = 1 + tOffset
  45.     len = tBytes.length - tOffset
  46.     ok = file_put_bytes(tFolder&tBasename&"."&tFileType, tBytes.readByteArray(len))
  47.    
  48.   else if dataType="kMoaCfFormat_PNG" then
  49.     tFileType = "png"
  50.     tOffset = 84
  51.     tBytes.position = 1 + tOffset
  52.     len = tBytes.length - tOffset
  53.     ok = file_put_bytes(tFolder&tBasename&"."&tFileType, tBytes.readByteArray(len))
  54.    
  55.   else -- BMP
  56.     tFileType = "bmp"
  57.     ok = file_put_bytes(tFolder&tBasename&"."&tFileType, BMP_encode(tMemRef.image))
  58.    
  59.   end if
  60.   return tFileType
  61. end
  62.  
  63. ----------------------------------------
  64. -- Exports sound member. If member is compressed, the original MP3/SWA data is saved,
  65. -- otherwise the uncompressed sound is saved as WAV file
  66. -- @param {string} tMemRef
  67. -- @param {string} tFolder
  68. -- @param {string} [tBaseName] - Optional, if omitted, memberName is used.
  69. -- @return {string|false} filetype ("mp3" or "swa" or "wav") or false
  70. ----------------------------------------
  71. on exportSoundMember (tSoundMemRef, tFolder, tBasename)
  72.   if tSoundMemRef.type<>#sound then return false
  73.   tBytes = cx_member_to_bytes(tSoundMemRef, true)
  74.   if ilk(tBytes)<>#bytearray then return false
  75.   if tBytes.length<29 then return false
  76.   formatLen = tBytes[25]
  77.   if formatLen<=18 then  
  78.     tBytes.position = 29
  79.     dataType = tBytes.readRawString(formatLen)
  80.   end if
  81.  
  82.   if dataType="kMoaCfFormat_MPEG3" or dataType="kMoaCfFormat_SWA" then
  83.    
  84.     if dataType="kMoaCfFormat_MPEG3" then -- MP3
  85.       tFileType = "mp3"
  86.     else if dataType="kMoaCfFormat_SWA" then -- SWA
  87.       tFileType = "swa"
  88.     end if
  89.     startPos = 29+formatLen+12
  90.     tBytes.position = startPos-4
  91.     len = tBytes.readInt32()
  92.     ok = file_put_bytes(tFolder&tBasename&"."&tFileType, tBytes.readByteArray(len))
  93.    
  94.   else -- uncompressed (WAV)
  95.    
  96.     tFileType = "wav"
  97.     fp = fopen(tFolder&tBasename&"."&tFileType, "wb")
  98.    
  99.     tBitRate = tSoundMemRef.sampleSize -- ???
  100.     tAvgBytesPerSec = tSoundMemRef.sampleRate * tBitRate/8 * tSoundMemRef.channelCount
  101.     tDataLen = tBytes.length-168
  102.     tBlockAlign = tBitRate/8 * tSoundMemRef.channelCount
  103.     tRiffSize = (16+8) + (tDataLen+8) + 4
  104.    
  105.     wavData = bytearray()
  106.     wavData.writeRawString("RIFF", 4)
  107.     wavData.writeInt32(tRiffSize)
  108.     wavData.writeRawString("WAVE", 4)
  109.    
  110.     -- fmt-CHUNK
  111.     wavData.writeRawString("fmt ", 4)
  112.     wavData.writeInt32(16) -- chunkSize
  113.     wavData.writeInt16(1) -- FormatTag
  114.     wavData.writeInt16(tSoundMemRef.channelCount) -- Channels
  115.     wavData.writeInt32(tSoundMemRef.sampleRate) -- SamplesPerSecond
  116.     wavData.writeInt32(tAvgBytesPerSec) -- AvgBytesPerSec
  117.     wavData.writeInt16(tBlockAlign) -- blockAlign
  118.     wavData.writeInt16(tBitRate)
  119.    
  120.     wavData.writeRawString("data", 4)
  121.     fwritebytes(fp, wavData)
  122.     tBytes.position = 169
  123.     fwritebytes(fp, tBytes.readBytearray(4))
  124.    
  125.     -- SWAP SAMPLE BYTE ORDER
  126.     ba = bytearray()
  127.     cnt = (tBytes.length-168-4) / 2
  128.     repeat with i = 1 to cnt
  129.       ba[i*2-1] = tBytes[168 + i*2]
  130.       ba[i*2]   = tBytes[168 + i*2-1]
  131.     end repeat
  132.    
  133.     ok = fwritebytes(fp, ba)
  134.    
  135.     fclose(fp)
  136.    
  137.   end if
  138.   return tFileType
  139. end
  140.  
  141. ----------------------------------------
  142. -- Exports flash member as SWF file
  143. -- @param {string} tMemRef
  144. -- @param {string} tFolder
  145. -- @param {string} [tBaseName] - Optional, if omitted, memberName is used.
  146. -- @return {string|false} filetype ("swf") or false
  147. ----------------------------------------
  148. on exportFlashMember (tMemRef, tFolder, tBasename)
  149.   if tMemRef.type<>#flash then return false  
  150.   tBytes = cx_member_to_bytes(tMemRef) -- FWS
  151.   tFileType = "swf"
  152.   tOffset = 285
  153.   tBytes.position = 1 + tOffset
  154.   len = tBytes.length - tOffset
  155.   ok = file_put_bytes(tFolder&tBasename&"."&tFileType, tBytes.readByteArray(len))
  156.   return tFileType
  157. end
  158.  
  159. --**************************************
  160. -- PRIVATE
  161. --**************************************
  162.  
  163. ----------------------------------------
  164. --
  165. ----------------------------------------
  166. on BMP_encode (tImage)
  167.  
  168.   tDepth = tImage.depth
  169.   if tDepth=32 and not tImage.useAlpha then tDepth=24
  170.  
  171.   palStr = bytearray()
  172.   palCnt = 0
  173.  
  174.   imgStr = bytearray()
  175.  
  176.   case (tDepth) of
  177.     1:
  178.       -- palette: white & black      
  179.       palStr.writeInt32(16777215)
  180.       palStr.writeInt32(0)
  181.       palCnt = 2
  182.      
  183.       -- The number of bytes for each row of pixels must be divisible by 4
  184.       padCnt = 3 - ( (tImage.width-1) / 8) mod 4
  185.       repeat with y=tImage.height-1 down to 0
  186.        
  187.         repeat with x=0 to tImage.width/8-1
  188.           c = 0
  189.           repeat with dx = 0 to 7
  190.             c = c + tImage.getPixel(x*8 + dx,y).paletteIndex * integer(power(2,7-dx))
  191.           end repeat
  192.           imgStr.writeInt8(c)
  193.         end repeat
  194.        
  195.         -- rest
  196.         w=x*8
  197.         if w<tImage.width then
  198.           c = 0
  199.           repeat with x=w to tImage.width-1
  200.             dx = x-w
  201.             c = c + tImage.getPixel(x,y).paletteIndex * integer(power(2,7-dx))          
  202.           end repeat
  203.           imgStr.writeInt8(c)
  204.         end if
  205.        
  206.         -- fill line
  207.         repeat with i = 1 to padCnt
  208.           imgStr.writeInt8(0)
  209.         end repeat
  210.        
  211.       end repeat
  212.      
  213.     4: -- 16 colors
  214.      
  215.       -- TEST: extract real palette from image itself
  216.       tTmpImg = image(tImage.width, tImage.height, 24)
  217.       tTmpImg.copyPixels(tImage, tTmpImg.rect, tTmpImg.rect)
  218.      
  219.       pal = []
  220.      
  221.       -- image data
  222.       padCnt = 3 - ( (tImage.width-1) / 2) mod 4
  223.       repeat with y=tImage.height-1 down to 0
  224.         repeat with x=0 to (tImage.width)/2-1
  225.           col1 = tTmpImg.getPixel(2*x,y)
  226.           if pal.count<256 then
  227.             if pal.getPos(col1)=0 then pal.add(col1)
  228.           end if
  229.           col2 = tTmpImg.getPixel(2*x+1,y)
  230.           if pal.count<256 then
  231.             if pal.getPos(col2)=0 then pal.add(col2)
  232.           end if
  233.          
  234.           c1 =  pal.getPos( col1 ) -1
  235.           c2 =  pal.getPos( col2 ) -1
  236.          
  237.           imgStr.writeInt8(c1*16+c2)
  238.         end repeat
  239.        
  240.         -- rest
  241.         if tImage.width mod 2 then
  242.           col1 = tTmpImg.getPixel(2*x,y)
  243.           if pal.count<256 then
  244.             if pal.getPos(col1)=0 then pal.add(col1)
  245.           end if
  246.          
  247.           c1 =  pal.getPos( col1 ) -1
  248.          
  249.           imgStr.writeInt8(c1*16)
  250.         end if
  251.        
  252.         -- fill line
  253.         repeat with i = 1 to padCnt
  254.           imgStr.writeInt8(0)
  255.         end repeat
  256.       end repeat
  257.      
  258.       -- create palette
  259.       repeat with col in pal
  260.         palStr.writeInt8(col.blue)
  261.         palStr.writeInt8(col.green)
  262.         palStr.writeInt8(col.red)
  263.         palStr.writeInt8(0)
  264.       end repeat
  265.       palCnt = count(pal)
  266.      
  267.       tTmpImg=VOID
  268.      
  269.     8:      
  270.       -- TEST: extract real palette from image itself
  271.       tTmpImg = image(tImage.width, tImage.height, 24)
  272.       tTmpImg.copyPixels(tImage, tTmpImg.rect, tTmpImg.rect)
  273.      
  274.       pal = []
  275.      
  276.       -- image data
  277.       padCnt = 3 - (tImage.width-1) mod 4
  278.      
  279.       repeat with y=tImage.height-1 down to 0
  280.         repeat with x=0 to tImage.width-1          
  281.           col = tTmpImg.getPixel(x,y)
  282.           pos = pal.getPos(col)
  283.           if pal.count<256 then
  284.             if pos=0 then
  285.               pal.add(col)
  286.               pos = pal.count
  287.             end if
  288.           end if
  289.           imgStr.writeInt8(pos-1)
  290.         end repeat
  291.        
  292.         -- fill line
  293.         repeat with i = 1 to padCnt
  294.           imgStr.writeInt8(0)
  295.         end repeat
  296.        
  297.       end repeat
  298.      
  299.       -- create palette
  300.       repeat with col in pal
  301.         palStr.writeInt8(col.blue)
  302.         palStr.writeInt8(col.green)
  303.         palStr.writeInt8(col.red)
  304.         palStr.writeInt8(0)
  305.       end repeat
  306.       palCnt = count(pal)
  307.      
  308.       tTmpImg=VOID
  309.      
  310.     16:
  311.       padCnt = (tImage.width mod 2) * 2
  312.       repeat with y=tImage.height-1 down to 0
  313.         repeat with x=0 to tImage.width-1          
  314.           col = tImage.getPixel(x,y)/8
  315.           n = col.red*32*32 + col.green*32 + col.blue --*2
  316.           imgStr.writeInt16(n)
  317.         end repeat
  318.        
  319.         -- fill line
  320.         repeat with i = 1 to padCnt
  321.           imgStr.writeInt8(0)
  322.         end repeat
  323.       end repeat
  324.      
  325.     24:  
  326.       padCnt = tImage.width mod 4
  327.       repeat with y=tImage.height-1 down to 0
  328.         repeat with x=0 to tImage.width-1
  329.           c = tImage.getPixel(x,y)
  330.           imgStr.writeInt8(c.blue)
  331.           imgStr.writeInt8(c.green)
  332.           imgStr.writeInt8(c.red)
  333.         end repeat
  334.         repeat with i = 1 to padCnt
  335.           imgStr.writeInt8(0)
  336.         end repeat
  337.       end repeat      
  338.      
  339.     32:
  340.       tAlphaImg = tImage.extractAlpha()      
  341.       repeat with y=tImage.height-1 down to 0
  342.         repeat with x=0 to tImage.width-1
  343.           c = tImage.getPixel(x,y)
  344.           imgStr.writeInt8(c.blue)
  345.           imgStr.writeInt8(c.green)
  346.           imgStr.writeInt8(c.red)
  347.           c = tAlphaImg.getPixel(x,y).paletteIndex
  348.           imgStr.writeInt8(c)
  349.         end repeat
  350.        
  351.       end repeat
  352.       tAlphaImg = VOID
  353.      
  354.     otherwise: -- incorrect depth!
  355.       return VOID
  356.      
  357.   end case
  358.  
  359.   -- create headers
  360.   bfOffBits = 54 + palCnt*4
  361.   rawSize = imgStr.length
  362.   bfSize = rawSize + bfOffBits
  363.  
  364.   -- bmpfileheader, 14 bytes
  365.   bmpfileheader = bytearray()
  366.   bmpfileheader.writeRawString("BM", 2)
  367.   bmpfileheader.writeInt32(bfSize)
  368.   bmpfileheader.writeInt32(0)
  369.   bmpfileheader.writeInt32(bfOffBits)
  370.  
  371.   -- bmpinfoheader, 40 bytes
  372.   bmpinfoheader = bytearray()
  373.   bmpinfoheader.writeInt32(40)                -- 1..4   --> biSize
  374.   bmpinfoheader.writeInt32(tImage.width)      -- 5..8   --> biWidth
  375.   bmpinfoheader.writeInt32(tImage.height)     -- 9..12  --> biHeight
  376.   bmpinfoheader.writeInt16(1)                 -- 13..14 --> biPlanes: 1
  377.   --bmpinfoheader.writeInt8(0)
  378.  
  379.   bmpinfoheader.writeInt16(tDepth)            -- 15..16 --> biBitCount: 1, 4, 8, 16, 24, or 32
  380.  
  381.   biCompression=0
  382.   bmpinfoheader.writeInt32(biCompression)     -- 17..20 --> biCompression: BI_RGB = 0, BI_RLE8 = 1
  383.  
  384.   bmpinfoheader.writeInt32(rawSize)           -- 21..24 --> biSizeImage
  385.  
  386.   --put chr(19)&chr(11)&chr(0)&chr(0) after bmpinfoheader -- 25..28 --> biXPelsPerMeter: 2835 = 72 dpi
  387.   bmpinfoheader.writeInt32(2835)              -- 25..28 --> biXPelsPerMeter: 2835 = 72 dpi
  388.  
  389.   --put chr(19)&chr(11)&chr(0)&chr(0) after bmpinfoheader -- 29..32 --> biYPelsPerMeter: 2835 = 72 dpi
  390.   bmpinfoheader.writeInt32(2835)              -- 29..32 --> biYPelsPerMeter: 2835 = 72 dpi
  391.  
  392.   bmpinfoheader.writeInt32(palCnt)            -- 29..32 --> biClrUsed
  393.   bmpinfoheader.writeInt32(palCnt)            -- 29..32 --> biClrImportant
  394.  
  395.   -- assemble file
  396.   bmpData = bytearray()
  397.   bmpData.writeByteArray(bmpfileheader)
  398.   bmpData.writeByteArray(bmpinfoheader)
  399.   bmpData.writeByteArray(palStr)
  400.   bmpData.writeByteArray(imgStr)
  401.   return bmpData
  402. end
  403.  
  404. ----------------------------------------
  405. -- saves ByteArray to file
  406. ----------------------------------------
  407. on file_put_bytes (tFile, tByteArray)
  408.   fp = xtra("fileIO").new()
  409.   if not objectP(fp) then return false
  410.   fp.openFile(tFile, 1)
  411.   err = fp.status()
  412.   if not (err) then fp.delete()
  413.   else if (err and not (err = -37)) then return false
  414.   fp.createFile(tFile)
  415.   err = fp.status()
  416.   if (err) then return false
  417.   fp.openFile(tFile, 2)
  418.   err = fp.status()
  419.   if (err) then return false
  420.   ok = fp.writeByteArray(tByteArray)
  421.   err = fp.status()
  422.   fp.closeFile()
  423.   fp=0
  424.   if (err) then return false
  425.   return true
  426. end
  427.  
  428. --------------------------------------
  429. -- opens file with specified mode, returns file pointer (xtra instance)
  430. -- tMode: r, rb, w, wb, a, ab, rw, rwb, wr, wrb
  431. --------------------------------------
  432. on fopen (tFile, tMode)
  433.   fp = xtra("fileIO").new()
  434.  
  435.   case (tMode) of
  436.     "r","rb": fp.openFile(tFile, 1)
  437.     "w","wb":
  438.       --if not fileExists(tFile) then
  439.       fp.createFile(tFile)
  440.       fp.openFile(tFile, 2)
  441.     "rw","wr","rwb","wrb": fp.openFile(tFile, 0)
  442.     "a","ab":
  443.       fp.openFile(tFile, 2)
  444.       fp.setPosition(fp.getLength())
  445.     otherwise:
  446.       return 0
  447.   end case
  448.  
  449.   if (fp.status()<>0) then fp = 0
  450.   return fp
  451. end
  452.  
  453. --------------------------------------
  454. -- closes open file
  455. --------------------------------------
  456. on fclose (fp)
  457.   fp.closeFile()
  458.   fp = 0
  459. end
  460.  
  461. --------------------------------------
  462. --
  463. --------------------------------------
  464. on fwritebytes (fp, ba)
  465.   return fp.writeByteArray(ba)
  466. end
  467.  
[raw code]