-- <<<- ------------------------------------------------------------------------ -- -- Director Toolkit Puppet Translator -- Don Hopkins, Kaleida Labs -- -- Purpose: -- Translates specially formatted director scores into ScriptX puppets. -- -- This file defines: -- class PuppetCastTranslator -- class PuppetScoreTranslator -- class PuppetTranslator -- function RegisterPuppetImporter -- ------------------------------------------------------------------------ -- -- This translates specially formatted director files into named frame -- sequences with named registration points, called "puppet parts". -- Each frame sequence is defined by a name, and one or more frames. -- The name is defined by the first frame label in the score, -- and the sequence continues until just before the next frame label. -- Each frame is defined by bitmaps that are composited together by the importer, -- and rectangles representing named registration points. -- A registration point is defined by a rectangular cast member in the score. -- The name of the rectangular cast member defines the registration point name. -- The center of the rectangle defines the registration point location. -- The importer does not draw the rectangles, but it returns their names and -- locations in a keyed list. -- -- parts ::= #(sequenceName: frameSequence, ...) -- sequenceName ::= from score frame label -- frameSequence ::= #(frame, ...) -- frame ::= #(bm, firstChannel, lastChannel, hotName, joints, sound, -- hotSpot, frameNumber, soundName) -- bm ::= bitmap composit of all bitmaps in frame -- firstChannel ::= first channel with bitmap -- lastChannel ::= last channel with bitmap -- hotName ::= name of first joint (hot spot), used as origin of bm -- joints ::= #(jointName: joint, ...) -- jointName ::= from rect cast member name -- joint ::= #(jointPoint, jointChannel, jointRect) -- jointPoint ::= registration point at center of rectangle, -- relative to bm origin 0,0 -- jointChannel ::= channel number of rectangle in score -- jointRect ::= bounding box of rectangle in score -- sound ::= AudioStream or or sound file name or undefined -- hotSpot ::= point, hot spot location in score -- frameNumber ::= frame number in score -- soundName ::= fileName or undefined ------------------------------------------------------------------------ if false then ( open TitleContainer path:"dirimp/dirimp.sxt" ) else ( open LibraryContainer \ dir: (spawn theStartDir "dtk/dirimp") \ path: "dirimp.sxl" ) module PuppetImporter uses DTK uses ScriptX end in module PuppetImporter ------------------------------------------------------------------------ class PuppetCastTranslator (DTKCastMemberToStencil, DTKCastMemberToAudioStream) end method translateBitmap self {class PuppetCastTranslator} bitmapCast -> ( local theStencil local regPoint := bitmapCast.registrationPt local oldBounds local newBounds -- Get the Bitmap theStencil := bitmapCast.bitmap oldBounds := theStencil.bbox -- Create a new bounds which is offset so that regPoint is at 0,0 newBounds := new Rect x1:((oldBounds.x1) - regPoint.x) \ x2:((oldBounds.x2) - regPoint.x) \ y1:((oldBounds.y1) - regPoint.y) \ y2:((oldBounds.y2) - regPoint.y) -- Create a new Bitmap, using the existing ColorMap and data theStencil := new BitMap bbox:newBounds \ colorMap:theStencil.colorMap \ data:theStencil.data \ bitsPerPixel:theStencil.bitsPerPixel theStencil ) method translateShape self {class PuppetCastTranslator} shapeCast -> ( local castName := shapeCast.castName as NameClass local box := shapeCast.bbox -- print #("translateShape", shapeCast, castName, box) #(castName, box) ) method translateSound self {class PuppetCastTranslator} soundCast -> ( local audioStream local fileName := soundCast.castFileName if (fileName == empty) do (fileName := undefined) if (fileName != undefined) then ( local dirFileName := self.dtk.directorFileName local dirFileDir := copy dirFileName deleteNth dirFileDir (size dirFileDir) local dir := spawn theRootDir dirFileDir local str := getStream dir fileName @readable audioStream := importMedia theImportExportEngine \ str @sound @aiff @AudioStream ) else ( audioStream := soundCast.audioStream ) if (audioStream == empty) do (audioStream := undefined) if ((fileName == undefined) and (audioStream == undefined)) then ( return undefined ) else ( return #(fileName, audioStream) ) ) method translateUnknown self {class PuppetCastTranslator} unknownCast -> ( undefined ) ------------------------------------------------------------------------ class PuppetScoreTranslator (DTKScoreTranslator) instance variables frames parts bgBrush mat compositedImages frameNumber end method init self {class PuppetScoreTranslator} #rest args -> ( apply nextMethod self args ) method prepareToTranslateScore self {class PuppetScoreTranslator} -> ( -- print #("prepareToTranslateScore", self) self.frames := new Array self.bgBrush := new Brush color: blueColor self.mat := new TwoDMatrix self.parts := #(:) self.compositedImages := #(:) self.frameNumber := 1 nextMethod self ) method translateFrame self {class PuppetScoreTranslator} \ prevFrame currentFrame changedArray -> ( local x, y local left := 1000, right := -1000, top := 1000, bottom := -1000 local frameImages := #() local joints := #(:) local hotSpot := undefined local hotName := @hotSpot local firstChannel := undefined local lastChannel := undefined local frameNumber := self.frameNumber self.frameNumber := frameNumber + 1 for chanNum := 1 to (size currentFrame.spriteChannels) do ( local chan := currentFrame.spriteChannels[chanNum] local castid := chan.castIndex local cast := self.castList[castid] if cast == empty do cast := undefined if (isAKindOf cast Bitmap) then ( if (firstChannel == undefined) do (firstChannel := chanNum) lastChannel := chanNum -- Collect bitmaps to composite together into a single frame. case (chan.ink) of @Copy: (cast.invisibleColor := undefined cast.matteColor := undefined) @Matte: (cast.invisibleColor := undefined cast.matteColor := whiteColor) @Invisible: (cast.matteColor := undefined cast.invisibleColor := whiteColor) otherwise: (cast.invisibleColor := undefined cast.matteColor := undefined) end x := chan.x y := chan.y local n, box := cast.bbox n := box.x1 + x if (n < left) do (left := n) n := box.x2 + x if (n > right) do (right := n) n := box.y1 + y if (n < top) do (top := n) n := box.y2 + y if (n > bottom) do (bottom := n) append frameImages #(x, y, castid) ) else ( if (isAKindOf cast Array) do ( local castName := cast[1] local scoreBox := new Rect \ x1: chan.x \ y1: chan.y \ x2: (chan.x + chan.width) \ y2: (chan.y + chan.height) -- Register the center of each rect as a joint. -- The first joint is the hot spot. local pt := new Point pt.x := floor ((scoreBox.x1 + scoreBox.x2) / 2) pt.y := floor ((scoreBox.y1 + scoreBox.y2) / 2) if (hotSpot == undefined) then ( hotSpot := copy pt hotName := castName ) else ( pt.x := pt.x - hotSpot.x pt.y := pt.y - hotSpot.y joints[castName] := #(pt, chanNum, new Rect x1: scoreBox.x1 \ y1: scoreBox.y1 \ x2: scoreBox.x2 \ y2: scoreBox.y2) ) ) ) ) if (hotSpot == undefined) do ( hotSpot := new Point x: left y: top ) local bm := undefined local bmFrame := empty if left == 1000 then ( left := 0 right := 0 top := 0 bottom := 0 hotSpot.x := 0 hotSpot.y := 0 bmFrame := #(undefined, 0) ) else ( left := left - hotSpot.x right := right - hotSpot.x top := top - hotSpot.y bottom := bottom - hotSpot.y bmFrame := self.compositedImages[#(frameImages, hotSpot.x, hotSpot.y)] ) -- Composite the bitmaps together into a single frame. if (bmFrame == empty) do ( bm := new BitmapSurface \ bbox: (new Rect x1:left y1:top x2:right y2:bottom) \ colormap: theDefault8Colormap local mat := self.mat erase bm self.bgBrush for a in frameImages do ( -- a contains #(x, y, castid) local x := a[1] local y := a[2] local castid := a[3] local b := self.castList[castid] local bms := b as BitmapSurface if (b.invisibleColor != undefined) do ( bms.invisibleColor := b.invisibleColor ) if (b.matteColor != undefined) do ( bms.matteColor := b.matteColor ) mat.tx := x - hotSpot.x mat.ty := y - hotSpot.y transfer bm bms bm mat ) bm := bm as Bitmap bm.invisibleColor := self.bgBrush.color bmFrame := #(bm, frameNumber) self.compositedImages[#(frameImages, hotSpot.x, hotSpot.y)] := bmFrame ) frameNumber := bmFrame[2] bm := bmFrame[1] local sound := undefined local i := currentFrame.soundChannels[1].castIndex if (i != undefined) do ( sound := self.castList[i] if (sound == empty) do (sound := undefined) ) if (sound == undefined) do ( i := currentFrame.soundChannels[2].castIndex if (i != undefined) do ( sound := self.castList[i] if (sound == empty) do (sound := undefined) ) ) local soundName := undefined local soundStream := undefined if (sound != undefined) do ( soundName := sound[1] soundStream := sound[2] ) -- print #((size self.frames), bm, firstChannel, lastChannel, -- hotName, joints, sound, hotSpot, frameNumber) append self.frames #( bm, firstChannel, lastChannel, hotName, joints, soundStream, hotSpot, frameNumber, soundName ) ) method finishTranslatingScore self {class PuppetScoreTranslator} -> ( -- print "finishTranslatingScore" local cont := self.dtk.container local labels := self.dtk.labelList if (size labels) == 0 do ( append labels (new Marker label: "Animate" start: 1) ) append labels (new Marker label: "End" start: ((size self.frames) + 1)) local acts := new Array for i := 1 to ((size labels) - 1) do ( local label := labels[i] local nextLabel := labels[i + 1] local firstFrame := label.start local lastFrame := nextLabel.start - 1 local act := (label.label as StringConstant) append acts act local frames := new Array initialSize: ((lastFrame - firstFrame) + 1) local f := self.frames for j := firstFrame to lastFrame do ( local frame := f[j] append frames frame ) -- print #(" action", act, "from", firstFrame, "to", lastFrame) self.parts[act] := frames ) ) ------------------------------------------------------------------------ class PuppetImporter (Importer) end method init self {class PuppetImporter} #rest args -> ( args := merge args #(@mediaCategory, @Metaphor, @inputMediaType, @Director, @outputMediaType, @PuppetParts) apply nextMethod self args ) method importFromStream self {class PuppetImporter} source #rest args #key \ container: \ outputMediaType: \ -> ( local theDTK local theScoreTranslator -- Make a DTK for the chosen file theDTK := new DTK directorFileName:source container:container theDTK.castTranslator := new PuppetCastTranslator theScoreTranslator := new PuppetScoreTranslator theDTK.scoreTranslator := theScoreTranslator translateDirector theDTK finishTranslatingScore theScoreTranslator return theScoreTranslator.parts ) ------------------------------------------------------------------------ function registerPuppetImporter -> ( local PuppetImporterRep local anArray local aPropertyCollection PuppetImporterRep := new ClassRepresentative \ owner:@OwnerSubstrate \ type:@TypeGestalt \ subtype: @subTypeImportExport \ version: "1.0" \ vendor: @VendorKaleida \ free: false addProperty PuppetImporterRep @mediaCategoryProp @Metaphor addProperty PuppetImporterRep @mediaInputTypeProp @Director anArray := #(@PuppetParts) aPropertyCollection := newPropertyCollection Manager anArray addProperty PuppetImporterRep @mediaOutputTypeProp aPropertyCollection setClass PuppetImporterRep PuppetImporter ) registerPuppetImporter() ------------------------------------------------------------------------ -- >>>