-- Prepare ImageGen gait poses; align bodies and retain coherent run anatomy. local src=app.open(app.params.source) local img=Image(src.cels[1].image) local cells=dofile(app.params.boxes) local target_h=tonumber(app.params.height) local head_cut=tonumber(app.params.head_cut) local is_fiddler=app.params.character=='fiddler' local source_native=app.params.source_native=='true' or is_fiddler local old=app.open(app.params.template) local old_img=Image(old.cels[1].image) for pixel in img:pixels() do local c=pixel() if app.pixelColor.rgbaA(c)>=192 then pixel(app.pixelColor.rgba(app.pixelColor.rgbaR(c),app.pixelColor.rgbaG(c),app.pixelColor.rgbaB(c),255)) else pixel(0) end end local maxw,maxh=0,0 for _,box in ipairs(cells) do maxw,maxh=math.max(maxw,box.w),math.max(maxh,box.h) end local factor=math.min(40/maxw,target_h/maxh) local atlas=Image(192,256,ColorMode.RGB) local idle_atlas=Image(48,256,ColorMode.RGB) local frames={} local function patch(dst,ref,x,y,w,h) for yy=y,y+h-1 do for xx=x,x+w-1 do dst:putPixel(xx,yy,ref:getPixel(xx,yy)) end end end local references={} if not source_native then for row=0,3 do local ref=Image(48,64,ColorMode.RGB) ref:drawImage(old_img,Point(-48,-row*64)) references[row+1]=ref local idle=Image(48,64,ColorMode.RGB) idle:drawImage(old_img,Point(-48,-row*64)) idle_atlas:drawImage(idle,Point(0,row*64)) end end for index,box in ipairs(cells) do local crop=Image(box.w,box.h,ColorMode.RGB) crop:drawImage(img,Point(-box.x,-box.y)) local tmp=Sprite(box.w,box.h,ColorMode.RGB) tmp.cels[1].image=crop local w,h=math.max(1,math.floor(box.w*factor+0.5)),math.max(1,math.floor(box.h*factor+0.5)) app.command.SpriteSize{ui=false,width=w,height=h,method='nearest-neighbor'} local frame=Image(48,64,ColorMode.RGB) local dx=math.floor(24-box.head_x*factor+0.5) dx=math.max(1,math.min(47-w,dx)) local row=math.floor((index-1)/4) frame:drawImage(tmp.cels[1].image,Point(dx,61-h)) -- Running has a forward-leaning head and neck. Pasting an upright idle head -- over it leaves the generated jaw below the seam (a second chin). -- Keep the complete generated run figure, including its own head and arms. if app.params.mode~='run' and not source_native then patch(frame,references[row+1],0,0,48,head_cut) end if app.params.mode~='run' and not source_native then -- A small torso core stays fixed; swinging sleeves/hands stay outside it. patch(frame,references[row+1],20,head_cut,9,math.max(0,43-head_cut)) end if app.params.mode=='run' and index%2==0 then local airborne=Image(48,64,ColorMode.RGB) airborne:drawImage(frame,Point(0,-2));frame=airborne end frames[index]=frame atlas:drawImage(frame,Point(((index-1)%4)*48,row*64)) tmp:close() end -- AI contact poses sometimes repeat the same leading leg. Correct only the -- central leg pixels; never mirror the asymmetric instrument or scabbard. if app.params.character=='commander' or is_fiddler then for _,row in ipairs({0,3}) do local start_y=app.params.character=='commander' and 46 or (row==3 and 52 or 49) for _,pair in ipairs({{3,1},{4,2}}) do local dst,ref=frames[row*4+pair[1]],frames[row*4+pair[2]] for y=start_y,63 do for x=14,33 do dst:putPixel(x,y,ref:getPixel(47-x,y)) end end end end atlas=Image(192,256,ColorMode.RGB) for index,frame in ipairs(frames) do atlas:drawImage(frame,Point(((index-1)%4)*48,math.floor((index-1)/4)*64)) end end if source_native then -- A revised full figure supplies its own idle; do not paste a superseded -- head or torso back onto the corrected character proportions. local cuts={} for cut in string.gmatch(app.params.source_head_cuts or '', '%d+') do table.insert(cuts,tonumber(cut)) end for row=0,3 do if cuts[row+1] then -- Per-view bounds include the complete jaw/neck but stop above the -- moving forearms. Use this source's own passing pose, never old art. local ref=Image(frames[row*4+2]) for col=0,3 do patch(frames[row*4+col+1],ref,0,0,48,cuts[row+1]) end end end atlas=Image(192,256,ColorMode.RGB) for index,frame in ipairs(frames) do atlas:drawImage(frame,Point(((index-1)%4)*48,math.floor((index-1)/4)*64)) end idle_atlas=Image(48,256,ColorMode.RGB) for row=0,3 do idle_atlas:drawImage(frames[row*4+2],Point(0,row*64)) end end old:close();src:close() local out=Sprite(192,256,ColorMode.RGB) out.cels[1].image=atlas;out:saveAs(app.params.output) app.command.SpriteSize{ui=false,width=576,height=768,method='nearest-neighbor'} out:saveAs(app.params.preview_prefix..'_sheet_3x.png');out:close() if app.params.idle then local idle=Sprite(48,256,ColorMode.RGB) idle.cels[1].image=idle_atlas;idle:saveAs(app.params.idle);idle:close() end local names={'south','west','east','north'} for row=0,3 do local gif=Sprite(48,64,ColorMode.RGB) for col=0,3 do if col>0 then gif:newFrame() end gif.cels[col+1].image=Image(frames[row*4+col+1]) gif.frames[col+1].duration=app.params.mode=='run' and 0.083333 or 0.125 end app.command.SpriteSize{ui=false,width=144,height=192,method='nearest-neighbor'} gif:saveAs(app.params.preview_prefix..'_'..names[row+1]..'.gif');gif:close() end print('Prepared16 gait frames; coherent run anatomy; head-based x alignment; factor '..factor)