--!movie
--!encoding=utf-8
--****************************************************************************
-- Software: File Export Library for Bitmap, Sound and Flash members
-- Version: 0.2
-- Date: 2015-08-12
-- Author: Valentin Schmidt
--
-- Requirements/Dependencies:
-- * Director 11.5+ (Win only?)
-- * Xtra "FileIO"
-- * Xtra "Crypto" (http://valentin.dasdeck.com/xtras/crypto_xtra/win/d11.5/)
--
-- ************************************************************************
----------------------------------------
-- Exports bitmap member. If member is compressed, the original JPG/PNG data is saved,
-- otherwise the uncompressed bitmap is saved as BMP file.
-- @param {string} tMemRef
-- @param {string} tFolder
-- @param {string} [tBaseName] - Optional, if omitted, memberName is used.
-- @return {string|false} filetype ("jpg" or "png" or "bmp") or false
----------------------------------------
on exportBitmapMember (tMemRef, tFolder, tBasename)
if tMemRef.type<>#bitmap then return false
tBytes = cx_member_to_bytes(tMemRef, true)
if ilk(tBytes)<>#bytearray then return false
if tBytes.length<29 then return false
if voidP(tBaseName) then tBaseName = tMemRef.name
pd = the last char of _movie.path
if the last char of tFolder<>pd then put pd after tFolder
formatLen = tBytes[25]
if formatLen<=18 then
tBytes.position = 29
dataType = tBytes.readRawString(formatLen)
end if
if dataType="kMoaCfFormat_JPEG" then
tFileType = "jpg"
tOffset = 85
tBytes.position = 1 + tOffset
len = tBytes.length - tOffset
ok = file_put_bytes(tFolder&tBasename&"."&tFileType, tBytes.readByteArray(len))
else if dataType="kMoaCfFormat_PNG" then
tFileType = "png"
tOffset = 84
tBytes.position = 1 + tOffset
len = tBytes.length - tOffset
ok = file_put_bytes(tFolder&tBasename&"."&tFileType, tBytes.readByteArray(len))
else -- BMP
tFileType = "bmp"
ok = file_put_bytes(tFolder&tBasename&"."&tFileType, BMP_encode(tMemRef.image))
end if
return tFileType
end
----------------------------------------
-- Exports sound member. If member is compressed, the original MP3/SWA data is saved,
-- otherwise the uncompressed sound is saved as WAV file
-- @param {string} tMemRef
-- @param {string} tFolder
-- @param {string} [tBaseName] - Optional, if omitted, memberName is used.
-- @return {string|false} filetype ("mp3" or "swa" or "wav") or false
----------------------------------------
on exportSoundMember (tSoundMemRef, tFolder, tBasename)
if tSoundMemRef.type<>#sound then return false
tBytes = cx_member_to_bytes(tSoundMemRef, true)
if ilk(tBytes)<>#bytearray then return false
if tBytes.length<29 then return false
formatLen = tBytes[25]
if formatLen<=18 then
tBytes.position = 29
dataType = tBytes.readRawString(formatLen)
end if
if dataType="kMoaCfFormat_MPEG3" or dataType="kMoaCfFormat_SWA" then
if dataType="kMoaCfFormat_MPEG3" then -- MP3
tFileType = "mp3"
else if dataType="kMoaCfFormat_SWA" then -- SWA
tFileType = "swa"
end if
startPos = 29+formatLen+12
tBytes.position = startPos-4
len = tBytes.readInt32()
ok = file_put_bytes(tFolder&tBasename&"."&tFileType, tBytes.readByteArray(len))
else -- uncompressed (WAV)
tFileType = "wav"
fp = fopen(tFolder&tBasename&"."&tFileType, "wb")
tBitRate = tSoundMemRef.sampleSize -- ???
tAvgBytesPerSec = tSoundMemRef.sampleRate * tBitRate/8 * tSoundMemRef.channelCount
tDataLen = tBytes.length-168
tBlockAlign = tBitRate/8 * tSoundMemRef.channelCount
tRiffSize = (16+8) + (tDataLen+8) + 4
wavData = bytearray()
wavData.writeRawString("RIFF", 4)
wavData.writeInt32(tRiffSize)
wavData.writeRawString("WAVE", 4)
-- fmt-CHUNK
wavData.writeRawString("fmt ", 4)
wavData.writeInt32(16) -- chunkSize
wavData.writeInt16(1) -- FormatTag
wavData.writeInt16(tSoundMemRef.channelCount) -- Channels
wavData.writeInt32(tSoundMemRef.sampleRate) -- SamplesPerSecond
wavData.writeInt32(tAvgBytesPerSec) -- AvgBytesPerSec
wavData.writeInt16(tBlockAlign) -- blockAlign
wavData.writeInt16(tBitRate)
wavData.writeRawString("data", 4)
fwritebytes(fp, wavData)
tBytes.position = 169
fwritebytes(fp, tBytes.readBytearray(4))
-- SWAP SAMPLE BYTE ORDER
ba = bytearray()
cnt = (tBytes.length-168-4) / 2
repeat with i = 1 to cnt
ba[i*2-1] = tBytes[168 + i*2]
ba[i*2] = tBytes[168 + i*2-1]
end repeat
ok = fwritebytes(fp, ba)
fclose(fp)
end if
return tFileType
end
----------------------------------------
-- Exports flash member as SWF file
-- @param {string} tMemRef
-- @param {string} tFolder
-- @param {string} [tBaseName] - Optional, if omitted, memberName is used.
-- @return {string|false} filetype ("swf") or false
----------------------------------------
on exportFlashMember (tMemRef, tFolder, tBasename)
if tMemRef.type<>#flash then return false
tBytes = cx_member_to_bytes(tMemRef) -- FWS
tFileType = "swf"
tOffset = 285
tBytes.position = 1 + tOffset
len = tBytes.length - tOffset
ok = file_put_bytes(tFolder&tBasename&"."&tFileType, tBytes.readByteArray(len))
return tFileType
end
--**************************************
-- PRIVATE
--**************************************
----------------------------------------
--
----------------------------------------
on BMP_encode (tImage)
tDepth = tImage.depth
if tDepth=32 and not tImage.useAlpha then tDepth=24
palStr = bytearray()
palCnt = 0
imgStr = bytearray()
case (tDepth) of
1:
-- palette: white & black
palStr.writeInt32(16777215)
palStr.writeInt32(0)
palCnt = 2
-- The number of bytes for each row of pixels must be divisible by 4
padCnt = 3 - ( (tImage.width-1) / 8) mod 4
repeat with y=tImage.height-1 down to 0
repeat with x=0 to tImage.width/8-1
c = 0
repeat with dx = 0 to 7
c = c + tImage.getPixel(x*8 + dx,y).paletteIndex * integer(power(2,7-dx))
end repeat
imgStr.writeInt8(c)
end repeat
-- rest
w=x*8
if w<tImage.width then
c = 0
repeat with x=w to tImage.width-1
dx = x-w
c = c + tImage.getPixel(x,y).paletteIndex * integer(power(2,7-dx))
end repeat
imgStr.writeInt8(c)
end if
-- fill line
repeat with i = 1 to padCnt
imgStr.writeInt8(0)
end repeat
end repeat
4: -- 16 colors
-- TEST: extract real palette from image itself
tTmpImg = image(tImage.width, tImage.height, 24)
tTmpImg.copyPixels(tImage, tTmpImg.rect, tTmpImg.rect)
pal = []
-- image data
padCnt = 3 - ( (tImage.width-1) / 2) mod 4
repeat with y=tImage.height-1 down to 0
repeat with x=0 to (tImage.width)/2-1
col1 = tTmpImg.getPixel(2*x,y)
if pal.count<256 then
if pal.getPos(col1)=0 then pal.add(col1)
end if
col2 = tTmpImg.getPixel(2*x+1,y)
if pal.count<256 then
if pal.getPos(col2)=0 then pal.add(col2)
end if
c1 = pal.getPos( col1 ) -1
c2 = pal.getPos( col2 ) -1
imgStr.writeInt8(c1*16+c2)
end repeat
-- rest
if tImage.width mod 2 then
col1 = tTmpImg.getPixel(2*x,y)
if pal.count<256 then
if pal.getPos(col1)=0 then pal.add(col1)
end if
c1 = pal.getPos( col1 ) -1
imgStr.writeInt8(c1*16)
end if
-- fill line
repeat with i = 1 to padCnt
imgStr.writeInt8(0)
end repeat
end repeat
-- create palette
repeat with col in pal
palStr.writeInt8(col.blue)
palStr.writeInt8(col.green)
palStr.writeInt8(col.red)
palStr.writeInt8(0)
end repeat
palCnt = count(pal)
tTmpImg=VOID
8:
-- TEST: extract real palette from image itself
tTmpImg = image(tImage.width, tImage.height, 24)
tTmpImg.copyPixels(tImage, tTmpImg.rect, tTmpImg.rect)
pal = []
-- image data
padCnt = 3 - (tImage.width-1) mod 4
repeat with y=tImage.height-1 down to 0
repeat with x=0 to tImage.width-1
col = tTmpImg.getPixel(x,y)
pos = pal.getPos(col)
if pal.count<256 then
if pos=0 then
pal.add(col)
pos = pal.count
end if
end if
imgStr.writeInt8(pos-1)
end repeat
-- fill line
repeat with i = 1 to padCnt
imgStr.writeInt8(0)
end repeat
end repeat
-- create palette
repeat with col in pal
palStr.writeInt8(col.blue)
palStr.writeInt8(col.green)
palStr.writeInt8(col.red)
palStr.writeInt8(0)
end repeat
palCnt = count(pal)
tTmpImg=VOID
16:
padCnt = (tImage.width mod 2) * 2
repeat with y=tImage.height-1 down to 0
repeat with x=0 to tImage.width-1
col = tImage.getPixel(x,y)/8
n = col.red*32*32 + col.green*32 + col.blue --*2
imgStr.writeInt16(n)
end repeat
-- fill line
repeat with i = 1 to padCnt
imgStr.writeInt8(0)
end repeat
end repeat
24:
padCnt = tImage.width mod 4
repeat with y=tImage.height-1 down to 0
repeat with x=0 to tImage.width-1
c = tImage.getPixel(x,y)
imgStr.writeInt8(c.blue)
imgStr.writeInt8(c.green)
imgStr.writeInt8(c.red)
end repeat
repeat with i = 1 to padCnt
imgStr.writeInt8(0)
end repeat
end repeat
32:
tAlphaImg = tImage.extractAlpha()
repeat with y=tImage.height-1 down to 0
repeat with x=0 to tImage.width-1
c = tImage.getPixel(x,y)
imgStr.writeInt8(c.blue)
imgStr.writeInt8(c.green)
imgStr.writeInt8(c.red)
c = tAlphaImg.getPixel(x,y).paletteIndex
imgStr.writeInt8(c)
end repeat
end repeat
tAlphaImg = VOID
otherwise: -- incorrect depth!
return VOID
end case
-- create headers
bfOffBits = 54 + palCnt*4
rawSize = imgStr.length
bfSize = rawSize + bfOffBits
-- bmpfileheader, 14 bytes
bmpfileheader = bytearray()
bmpfileheader.writeRawString("BM", 2)
bmpfileheader.writeInt32(bfSize)
bmpfileheader.writeInt32(0)
bmpfileheader.writeInt32(bfOffBits)
-- bmpinfoheader, 40 bytes
bmpinfoheader = bytearray()
bmpinfoheader.writeInt32(40) -- 1..4 --> biSize
bmpinfoheader.writeInt32(tImage.width) -- 5..8 --> biWidth
bmpinfoheader.writeInt32(tImage.height) -- 9..12 --> biHeight
bmpinfoheader.writeInt16(1) -- 13..14 --> biPlanes: 1
--bmpinfoheader.writeInt8(0)
bmpinfoheader.writeInt16(tDepth) -- 15..16 --> biBitCount: 1, 4, 8, 16, 24, or 32
biCompression=0
bmpinfoheader.writeInt32(biCompression) -- 17..20 --> biCompression: BI_RGB = 0, BI_RLE8 = 1
bmpinfoheader.writeInt32(rawSize) -- 21..24 --> biSizeImage
--put chr(19)&chr(11)&chr(0)&chr(0) after bmpinfoheader -- 25..28 --> biXPelsPerMeter: 2835 = 72 dpi
bmpinfoheader.writeInt32(2835) -- 25..28 --> biXPelsPerMeter: 2835 = 72 dpi
--put chr(19)&chr(11)&chr(0)&chr(0) after bmpinfoheader -- 29..32 --> biYPelsPerMeter: 2835 = 72 dpi
bmpinfoheader.writeInt32(2835) -- 29..32 --> biYPelsPerMeter: 2835 = 72 dpi
bmpinfoheader.writeInt32(palCnt) -- 29..32 --> biClrUsed
bmpinfoheader.writeInt32(palCnt) -- 29..32 --> biClrImportant
-- assemble file
bmpData = bytearray()
bmpData.writeByteArray(bmpfileheader)
bmpData.writeByteArray(bmpinfoheader)
bmpData.writeByteArray(palStr)
bmpData.writeByteArray(imgStr)
return bmpData
end
----------------------------------------
-- saves ByteArray to file
----------------------------------------
on file_put_bytes (tFile, tByteArray)
fp = xtra("fileIO").new()
if not objectP(fp) then return false
fp.openFile(tFile, 1)
err = fp.status()
if not (err) then fp.delete()
else if (err and not (err = -37)) then return false
fp.createFile(tFile)
err = fp.status()
if (err) then return false
fp.openFile(tFile, 2)
err = fp.status()
if (err) then return false
ok = fp.writeByteArray(tByteArray)
err = fp.status()
fp.closeFile()
fp=0
if (err) then return false
return true
end
--------------------------------------
-- opens file with specified mode, returns file pointer (xtra instance)
-- tMode: r, rb, w, wb, a, ab, rw, rwb, wr, wrb
--------------------------------------
on fopen (tFile, tMode)
fp = xtra("fileIO").new()
case (tMode) of
"r","rb": fp.openFile(tFile, 1)
"w","wb":
--if not fileExists(tFile) then
fp.createFile(tFile)
fp.openFile(tFile, 2)
"rw","wr","rwb","wrb": fp.openFile(tFile, 0)
"a","ab":
fp.openFile(tFile, 2)
fp.setPosition(fp.getLength())
otherwise:
return 0
end case
if (fp.status()<>0) then fp = 0
return fp
end
--------------------------------------
-- closes open file
--------------------------------------
on fclose (fp)
fp.closeFile()
fp = 0
end
--------------------------------------
--
--------------------------------------
on fwritebytes (fp, ba)
return fp.writeByteArray(ba)
end