/* JWildfire - an image and animation processor written in Java Copyright (C) 1995-2016 Andreas Maschke This is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this software; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jwildfire.create.tina.render; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.List; import org.jwildfire.base.Prefs; import org.jwildfire.base.QualityProfile; import org.jwildfire.base.ThreadTools; import org.jwildfire.base.mathlib.MathLib; import org.jwildfire.create.tina.animate.AnimationService; import org.jwildfire.create.tina.base.Flame; import org.jwildfire.create.tina.base.Layer; import org.jwildfire.create.tina.base.Stereo3dColor; import org.jwildfire.create.tina.base.Stereo3dEye; import org.jwildfire.create.tina.base.Stereo3dMode; import org.jwildfire.create.tina.base.raster.AbstractRaster; import org.jwildfire.create.tina.random.AbstractRandomGenerator; import org.jwildfire.create.tina.random.RandomGeneratorFactory; import org.jwildfire.create.tina.render.image.PostFilterImageThread; import org.jwildfire.create.tina.render.image.RenderHDRImageThread; import org.jwildfire.create.tina.render.image.RenderImageSimpleScaledThread; import org.jwildfire.create.tina.render.image.RenderImageSimpleThread; import org.jwildfire.create.tina.render.image.RenderImageThread; import org.jwildfire.create.tina.render.image.RenderZBufferThread; import org.jwildfire.create.tina.render.postdof.PostDOFBuffer; import org.jwildfire.create.tina.render.postdof.PostDOFCalculator; import org.jwildfire.create.tina.variation.FlameTransformationContext; import org.jwildfire.create.tina.variation.RessourceManager; import org.jwildfire.image.Pixel; import org.jwildfire.image.SimpleGrayImage; import org.jwildfire.image.SimpleHDRImage; import org.jwildfire.image.SimpleImage; import org.jwildfire.io.ImageWriter; import org.jwildfire.transform.ComposeTransformer; import org.jwildfire.transform.ComposeTransformer.HAlignment; import org.jwildfire.transform.ComposeTransformer.VAlignment; import org.jwildfire.transform.ScaleAspect; import org.jwildfire.transform.ScaleTransformer; public class FlameRenderer { // constants private final static int MAX_FILTER_WIDTH = 25; // init in initRaster protected int imageWidth; protected int imageHeight; int rasterWidth; int rasterHeight; private int rasterSize; protected int borderWidth; protected int maxBorderWidth; private boolean withAlpha; LogDensityFilter logDensityFilter; GammaCorrectionFilter gammaCorrectionFilter; private AbstractRaster raster; private int oversample; // init in initView private int renderScale = 1; protected AbstractRandomGenerator randGen; // private ProgressUpdater progressUpdater; private int progressDisplayPhaseCount = 1; private int progressChangePerPhase = 0; private int progressDisplayPhase = 0; // protected final FlameTransformationContext flameTransformationContext; private RenderInfo renderInfo; protected final Flame flame; private final Prefs prefs; private boolean preview; private List<IterationObserver> iterationObservers; private List<AbstractRenderThread> runningThreads; private boolean forceAbort; private Stereo3dEye eye = Stereo3dEye.UNSPECIFIED; public void deregisterIterationObserver(IterationObserver pObserver) { if (iterationObservers != null) iterationObservers.remove(pObserver); } public void registerIterationObserver(IterationObserver pObserver) { if (iterationObservers == null) { iterationObservers = new ArrayList<IterationObserver>(); } else if (iterationObservers.indexOf(pObserver) >= 0) { return; } iterationObservers.add(pObserver); } public FlameRenderer(Flame pFlame, Prefs pPrefs, boolean pWithAlpha, boolean pPreview) { flame = pFlame; if (flame.getSolidRenderSettings().isSolidRenderingEnabled()) { flame.setAntialiasAmount(0.0); flame.setAntialiasRadius(0.0); } prefs = pPrefs; withAlpha = pWithAlpha; preview = pPreview; randGen = RandomGeneratorFactory.getInstance(prefs, prefs.getTinaRandomNumberGenerator()); flameTransformationContext = new FlameTransformationContext(this, randGen, flame.getFrame()); flameTransformationContext.setPreserveZCoordinate(pFlame.isPreserveZ()); flameTransformationContext.setPreview(pPreview); } public void initRasterSizes(int pImageWidth, int pImageHeight) { Flame flameForInit; if (flame.hasPreRenderMotionProperty()) { double time = flame.getFrame() >= 0 ? flame.getFrame() : 0; flameForInit = AnimationService.evalMotionCurves(flame.makeCopy(), time); } else { flameForInit = flame; } imageWidth = pImageWidth; imageHeight = pImageHeight; oversample = flame.getSpatialOversampling(); logDensityFilter = new LogDensityFilter(flameForInit, randGen); maxBorderWidth = (MAX_FILTER_WIDTH - oversample) / 2; borderWidth = (logDensityFilter.getNoiseFilterSize() - oversample) / 2; rasterWidth = oversample * imageWidth + 2 * maxBorderWidth; rasterHeight = oversample * imageHeight + 2 * maxBorderWidth; gammaCorrectionFilter = new GammaCorrectionFilter(flameForInit, withAlpha, rasterWidth, rasterHeight); rasterSize = rasterWidth * rasterHeight; } private void initRaster(RenderInfo pRenderInfo, int pImageWidth, int pImageHeight, double pSampleDensity) { initRasterSizes(pImageWidth, pImageHeight); if (pRenderInfo.getRestoredRaster() != null) { if (rasterWidth != pRenderInfo.getRestoredRaster().getRasterWidth()) { throw new RuntimeException("Raster width does not match (" + rasterWidth + " != " + pRenderInfo.getRestoredRaster().getRasterWidth() + ")"); } if (rasterHeight != pRenderInfo.getRestoredRaster().getRasterHeight()) { throw new RuntimeException("Raster height does not match (" + rasterHeight + " != " + pRenderInfo.getRestoredRaster().getRasterHeight() + ")"); } raster = pRenderInfo.getRestoredRaster(); } else { raster = allocRaster(oversample, pSampleDensity); } } private AbstractRaster allocRaster(int pOversample, double pSampleDensity) { Class<? extends AbstractRaster> rasterClass = prefs.getTinaRasterType().getRasterClass(flame); AbstractRaster raster; try { raster = rasterClass.newInstance(); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } raster.allocRaster(flame, rasterWidth, rasterHeight, pOversample, pSampleDensity); return raster; } public RenderedFlame finishRenderFlame(long pSampleCount) { if (renderInfo == null) { throw new IllegalStateException(); } double oldDensity = flame.getSampleDensity(); try { double quality = logDensityFilter.calcDensity(pSampleCount, rasterSize); flame.setSampleDensity(quality); RenderedFlame res = new RenderedFlame(); res.init(renderInfo, flame); renderImage(res.getImage(), res.getHDRImage(), res.getZBuffer()); return res; } finally { flame.setSampleDensity(oldDensity); } } public RenderedFlame finishZBuffer(long pSampleCount) { if (renderInfo == null) { throw new IllegalStateException(); } double oldDensity = flame.getSampleDensity(); try { double quality = logDensityFilter.calcDensity(pSampleCount, rasterSize); flame.setSampleDensity(quality); RenderedFlame res = new RenderedFlame(); RenderInfo tmpRenderInfo = new RenderInfo(); tmpRenderInfo.assign(renderInfo); tmpRenderInfo.setRenderImage(false); tmpRenderInfo.setRenderHDR(false); tmpRenderInfo.setRenderZBuffer(true); res.init(tmpRenderInfo, flame); renderImage(res.getImage(), res.getHDRImage(), res.getZBuffer()); return res; } finally { flame.setSampleDensity(oldDensity); } } public RenderedFlame renderFlame(RenderInfo pRenderInfo) { renderInfo = pRenderInfo; if (!Stereo3dMode.NONE.equals(flame.getStereo3dMode())) { return renderImageStereo3d(pRenderInfo); } else { return renderImageNormal(pRenderInfo, 1, 0); } } private RenderedFlame renderImageStereo3d(RenderInfo pRenderInfo) { Stereo3dEye storedEye = eye; double storedAngle = flame.getStereo3dAngle(); double storedEyeDist = flame.getStereo3dEyeDist(); try { switch (pRenderInfo.getRenderMode()) { case PREVIEW: switch (flame.getStereo3dPreview()) { case SIDE_BY_SIDE: { RenderedFlame result = renderStereo3dSideBySide(pRenderInfo); ScaleTransformer scaleTransformer = new ScaleTransformer(); scaleTransformer.setAspect(ScaleAspect.IGNORE); scaleTransformer.setScaleWidth(pRenderInfo.getImageWidth() * renderScale); scaleTransformer.setScaleHeight(pRenderInfo.getImageHeight() * renderScale); scaleTransformer.transformImage(result.getImage()); return result; } case SIDE_BY_SIDE_FULL: { return renderStereo3dSideBySide(pRenderInfo); } case ANAGLYPH: { return renderStereo3dAnaglyph(pRenderInfo); } case NONE: return renderImageNormal(pRenderInfo, 1, 0); default: throw new IllegalStateException(pRenderInfo.getRenderMode().toString()); } case PRODUCTION: switch (flame.getStereo3dMode()) { case ANAGLYPH: return renderStereo3dAnaglyph(pRenderInfo); case INTERPOLATED_IMAGES: { RenderInfo localRenderInfo = pRenderInfo.makeCopy(); localRenderInfo.setRenderHDR(false); localRenderInfo.setRenderZBuffer(false); RenderedFlame leftRenders[] = new RenderedFlame[flame.getStereo3dInterpolatedImageCount()]; double dAngle = storedAngle / (double) leftRenders.length; double dEyeDist = storedEyeDist / (double) leftRenders.length; int totalImageCount = 2 * leftRenders.length; int totalImageIdx = 0; for (int i = 0; i < leftRenders.length; i++) { eye = Stereo3dEye.LEFT; flame.setStereo3dAngle((i + 1) * dAngle); flame.setStereo3dEyeDist((i + 1) * dEyeDist); leftRenders[i] = renderImageNormal(localRenderInfo, totalImageCount, totalImageIdx++); } RenderedFlame rightRenders[] = new RenderedFlame[flame.getStereo3dInterpolatedImageCount()]; for (int i = 0; i < rightRenders.length; i++) { eye = Stereo3dEye.RIGHT; flame.setStereo3dAngle((i + 1) * dAngle); flame.setStereo3dEyeDist((i + 1) * dEyeDist); rightRenders[i] = renderImageNormal(localRenderInfo, totalImageCount, totalImageIdx++); } if (flame.isStereo3dSwapSides()) { RenderedFlame tmp[] = leftRenders; leftRenders = rightRenders; rightRenders = tmp; } RenderedFlame mergedRender = new RenderedFlame(); localRenderInfo.setImageWidth(2 * pRenderInfo.getImageWidth()); localRenderInfo.setImageHeight(leftRenders.length * pRenderInfo.getImageHeight()); mergedRender.init(localRenderInfo, flame); SimpleImage mergedImg = mergedRender.getImage(); ComposeTransformer composeTransformer = new ComposeTransformer(); composeTransformer.setHAlign(HAlignment.OFF); composeTransformer.setVAlign(VAlignment.OFF); int yOff = 0; for (int i = 0; i < leftRenders.length; i++) { composeTransformer.setLeft(0); composeTransformer.setTop(yOff); composeTransformer.setForegroundImage(leftRenders[i].getImage()); composeTransformer.transformImage(mergedImg); yOff += pRenderInfo.getImageHeight(); } yOff = 0; for (int i = 0; i < rightRenders.length; i++) { composeTransformer.setLeft(pRenderInfo.getImageWidth()); composeTransformer.setTop(yOff); composeTransformer.setForegroundImage(rightRenders[i].getImage()); composeTransformer.transformImage(mergedImg); yOff += pRenderInfo.getImageHeight(); } return mergedRender; } case SIDE_BY_SIDE: return renderStereo3dSideBySide(pRenderInfo); case NONE: return renderImageNormal(pRenderInfo, 1, 0); default: throw new IllegalStateException(flame.getStereo3dMode().toString()); } default: throw new IllegalStateException(pRenderInfo.getRenderMode().toString()); } } finally { eye = storedEye; flame.setStereo3dAngle(storedAngle); flame.setStereo3dEyeDist(storedEyeDist); } } private RenderedFlame renderStereo3dSideBySide(RenderInfo pRenderInfo) { RenderInfo localRenderInfo = pRenderInfo.makeCopy(); localRenderInfo.setRenderHDR(false); localRenderInfo.setRenderZBuffer(false); eye = Stereo3dEye.LEFT; RenderedFlame leftRender = renderImageNormal(localRenderInfo, 2, 0); eye = Stereo3dEye.RIGHT; RenderedFlame rightRender = renderImageNormal(localRenderInfo, 2, 1); if (flame.isStereo3dSwapSides()) { RenderedFlame tmp = leftRender; leftRender = rightRender; rightRender = tmp; } RenderedFlame mergedRender = new RenderedFlame(); localRenderInfo.setImageWidth(2 * leftRender.getImage().getImageWidth()); localRenderInfo.setImageHeight(leftRender.getImage().getImageHeight()); mergedRender.init(localRenderInfo, flame); SimpleImage mergedImg = mergedRender.getImage(); ComposeTransformer composeTransformer = new ComposeTransformer(); composeTransformer.setHAlign(HAlignment.OFF); composeTransformer.setVAlign(VAlignment.OFF); composeTransformer.setForegroundImage(leftRender.getImage()); composeTransformer.transformImage(mergedImg); composeTransformer.setForegroundImage(rightRender.getImage()); composeTransformer.setLeft(leftRender.getImage().getImageWidth()); composeTransformer.transformImage(mergedImg); return mergedRender; } private RenderedFlame renderStereo3dAnaglyph(RenderInfo pRenderInfo) { RenderInfo localRenderInfo = pRenderInfo.makeCopy(); localRenderInfo.setRenderHDR(false); localRenderInfo.setRenderZBuffer(false); eye = Stereo3dEye.LEFT; RenderedFlame leftRender = renderImageNormal(localRenderInfo, 2, 0); eye = Stereo3dEye.RIGHT; RenderedFlame rightRender = renderImageNormal(localRenderInfo, 2, 1); if (flame.isStereo3dSwapSides()) { RenderedFlame tmp = leftRender; leftRender = rightRender; rightRender = tmp; } Pixel lPixel = new Pixel(); Pixel rPixel = new Pixel(); Stereo3dColor leftColor = flame.getAnaglyph3dLeftEyeColor(); Stereo3dColor rightColor = flame.getAnaglyph3dRightEyeColor(); SimpleImage leftImg = leftRender.getImage(); SimpleImage rightImg = rightRender.getImage(); RenderedFlame mergedRender = new RenderedFlame(); localRenderInfo.setImageWidth(leftRender.getImage().getImageWidth()); localRenderInfo.setImageHeight(leftRender.getImage().getImageHeight()); mergedRender.init(localRenderInfo, flame); SimpleImage mergedImg = mergedRender.getImage(); for (int i = 0; i < mergedImg.getImageHeight(); i++) { for (int j = 0; j < mergedImg.getImageWidth(); j++) { lPixel.setARGBValue(leftImg.getARGBValue(j, i)); rPixel.setARGBValue(rightImg.getARGBValue(j, i)); int mr = leftColor.calculateRed(lPixel.r, lPixel.g, lPixel.b) + rightColor.calculateRed(rPixel.r, rPixel.g, rPixel.b); if (mr < 0) mr = 0; else if (mr > 255) mr = 255; int mg = leftColor.calculateGreen(lPixel.r, lPixel.g, lPixel.b) + rightColor.calculateGreen(rPixel.r, rPixel.g, rPixel.b); if (mg < 0) mg = 0; else if (mg > 255) mg = 255; int mb = leftColor.calculateBlue(lPixel.r, lPixel.g, lPixel.b) + rightColor.calculateBlue(rPixel.r, rPixel.g, rPixel.b); if (mb < 0) mb = 0; else if (mb > 255) mb = 255; mergedImg.setRGB(j, i, mr, mg, mb); } } return mergedRender; } private RenderedFlame renderImageNormal(RenderInfo pRenderInfo, int pTotalImagePartCount, int pTotalImagePartIdx) { progressDisplayPhaseCount = pTotalImagePartCount; progressDisplayPhase = pTotalImagePartIdx; RenderedFlame res = new RenderedFlame(); res.init(pRenderInfo, flame); if (forceAbort) return res; boolean renderNormal = true; boolean renderHDR = pRenderInfo.isRenderHDR(); if (!flame.isRenderable()) { if (renderNormal) { if (renderScale > 0) { res.getImage().resetImage(res.getImage().getImageWidth() * renderScale, res.getImage().getImageHeight() * renderScale); } if (flame.getBGImageFilename().length() > 0) { try { res.getImage().fillBackground((SimpleImage) RessourceManager.getImage(flame.getBGImageFilename())); } catch (Exception ex) { ex.printStackTrace(); } } else { new FlameBGColorHandler(flame).fillBackground(res.getImage()); } } if (renderHDR) { res.getHDRImage().fillBackground(flame.getBgColorRed(), flame.getBgColorGreen(), flame.getBgColorBlue()); try { res.getHDRImage().fillBackground((SimpleImage) RessourceManager.getImage(flame.getBGImageFilename())); } catch (Exception ex) { ex.printStackTrace(); } } return res; } double origZoom = flame.getCamZoom(); try { SimpleImage img = renderNormal ? res.getImage() : null; SimpleHDRImage hdrImg = renderHDR ? res.getHDRImage() : null; if (renderNormal) { initRaster(pRenderInfo, img.getImageWidth(), img.getImageHeight(), flame.getSampleDensity()); } else if (renderHDR) { initRaster(pRenderInfo, hdrImg.getImageWidth(), hdrImg.getImageHeight(), flame.getSampleDensity()); } else { throw new IllegalStateException(); } if (pRenderInfo.getRestoredRaster() == null) { List<List<RenderPacket>> renderFlames = new ArrayList<List<RenderPacket>>(); for (int t = 0; t < prefs.getTinaRenderThreads(); t++) { renderFlames.add(createRenderPackets(flame, flame.getFrame())); } forceAbort = false; iterate(0, 1, renderFlames, null); raster.finalizeRaster(); if (pRenderInfo.isStoreRaster()) { res.setRaster(raster); } } else { if (flame.getSolidRenderSettings().isSolidRenderingEnabled()) { FlameRendererView view = createView(flame); raster.notifyInit(view.getLightViewCalculator()); } } if (!forceAbort) { if ((flame.getSampleDensity() <= 10.0 && flame.getSpatialFilterRadius() <= MathLib.EPSILON) || renderScale > 1) { renderImageSimple(img); } else { renderImage(img, hdrImg, res.getZBuffer()); } } } finally { flame.setCamZoom(origZoom); } return res; } private void renderImage(SimpleImage pImage, SimpleHDRImage pHDRImage, SimpleGrayImage pZBufferImg) { if (renderScale > 1) { throw new IllegalArgumentException("renderScale != 1"); } if (pImage != null) { logDensityFilter.setRaster(raster, rasterWidth, rasterHeight, pImage.getImageWidth(), pImage.getImageHeight()); } else if (pZBufferImg != null) { logDensityFilter.setRaster(raster, rasterWidth, rasterHeight, pZBufferImg.getImageWidth(), pZBufferImg.getImageHeight()); } else if (pHDRImage != null) { logDensityFilter.setRaster(raster, rasterWidth, rasterHeight, pHDRImage.getImageWidth(), pHDRImage.getImageHeight()); } else { throw new IllegalStateException(); } renderImage(pImage); if (flame.isPostNoiseFilter() && flame.getPostNoiseFilterThreshold() > MathLib.EPSILON) { postFilterImage(pImage); } renderHDRImage(pHDRImage); renderZBuffer(pZBufferImg); } private void renderZBuffer(SimpleGrayImage pGreyImage) { double zScale = 0.001 * flame.getZBufferScale(); if (pGreyImage != null) { int threadCount = prefs.getTinaRenderThreads(); if (threadCount < 1 || pGreyImage.getImageHeight() < 8 * threadCount) { threadCount = 1; } int rowsPerThread = pGreyImage.getImageHeight() / threadCount; List<RenderZBufferThread> threads = new ArrayList<>(); for (int i = 0; i < threadCount; i++) { int startRow = i * rowsPerThread; int endRow = i < threadCount - 1 ? startRow + rowsPerThread : pGreyImage.getImageHeight(); RenderZBufferThread thread = new RenderZBufferThread(flame, logDensityFilter, startRow, endRow, pGreyImage, zScale); threads.add(thread); if (threadCount > 1) { new Thread(thread).start(); } else { thread.run(); } } ThreadTools.waitForThreads(threadCount, threads); } } private void renderHDRImage(SimpleHDRImage pHDRImage) { if (pHDRImage != null) { int threadCount = prefs.getTinaRenderThreads(); if (threadCount < 1 || pHDRImage.getImageHeight() < 8 * threadCount) { threadCount = 1; } int rowsPerThread = pHDRImage.getImageHeight() / threadCount; PostDOFBuffer dofBuffer = flame.getCamDOF() > MathLib.EPSILON && flame.getSolidRenderSettings().isSolidRenderingEnabled() ? new PostDOFBuffer(pHDRImage) : null; List<RenderHDRImageThread> threads = new ArrayList<>(); for (int i = 0; i < threadCount; i++) { int startRow = i * rowsPerThread; int endRow = i < threadCount - 1 ? startRow + rowsPerThread : pHDRImage.getImageHeight(); RenderHDRImageThread thread = new RenderHDRImageThread(flame, logDensityFilter, gammaCorrectionFilter, startRow, endRow, pHDRImage, dofBuffer != null ? new PostDOFCalculator(dofBuffer, flame) : null); threads.add(thread); if (threadCount > 1) { new Thread(thread).start(); } else { thread.run(); } } ThreadTools.waitForThreads(threadCount, threads); if (dofBuffer != null) { dofBuffer.renderToImage(pHDRImage); } } } private void renderImage(SimpleImage pImage) { if (pImage != null) { int threadCount = prefs.getTinaRenderThreads(); if (threadCount < 1 || pImage.getImageHeight() < 8 * threadCount) { threadCount = 1; } PostDOFBuffer dofBuffer = flame.getCamDOF() > MathLib.EPSILON && flame.getSolidRenderSettings().isSolidRenderingEnabled() ? new PostDOFBuffer(pImage) : null; int rowsPerThread = pImage.getImageHeight() / threadCount; List<RenderImageThread> threads = new ArrayList<RenderImageThread>(); for (int i = 0; i < threadCount; i++) { int startRow = i * rowsPerThread; int endRow = i < threadCount - 1 ? startRow + rowsPerThread : pImage.getImageHeight(); RenderImageThread thread = new RenderImageThread(flame, logDensityFilter, gammaCorrectionFilter, startRow, endRow, pImage, dofBuffer != null ? new PostDOFCalculator(dofBuffer, flame) : null); threads.add(thread); if (threadCount > 1) { new Thread(thread).start(); } else { thread.run(); } } ThreadTools.waitForThreads(threadCount, threads); if (dofBuffer != null) { dofBuffer.renderToImage(pImage); } } } private void postFilterImage(SimpleImage pImage) { if (pImage != null) { int threadCount = prefs.getTinaRenderThreads(); if (threadCount < 1 || pImage.getImageHeight() < 8 * threadCount) { threadCount = 1; } int rowsPerThread = pImage.getImageHeight() / threadCount; SimpleImage input = pImage.clone(); List<PostFilterImageThread> threads = new ArrayList<PostFilterImageThread>(); for (int i = 0; i < threadCount; i++) { int startRow = i * rowsPerThread; int endRow = i < threadCount - 1 ? startRow + rowsPerThread : pImage.getImageHeight(); PostFilterImageThread thread = new PostFilterImageThread(startRow, endRow, input, pImage, flame.getPostNoiseFilterThreshold()); threads.add(thread); if (threadCount > 1) { new Thread(thread).start(); } else { thread.run(); } } ThreadTools.waitForThreads(threadCount, threads); } } private void renderImageSimple(SimpleImage pImage) { int threadCount = prefs.getTinaRenderThreads(); if (threadCount < 1) threadCount = 1; logDensityFilter.setRaster(raster, rasterWidth, rasterHeight, pImage.getImageWidth(), pImage.getImageHeight()); if (renderScale == 2) { SimpleImage newImg = new SimpleImage(pImage.getImageWidth() * renderScale, pImage.getImageHeight() * renderScale); int rowsPerThread = pImage.getImageHeight() / threadCount; List<RenderImageSimpleScaledThread> threads = new ArrayList<RenderImageSimpleScaledThread>(); for (int i = 0; i < threadCount; i++) { int startRow = i * rowsPerThread; int endRow = i < threadCount - 1 ? startRow + rowsPerThread : pImage.getImageHeight(); RenderImageSimpleScaledThread thread = new RenderImageSimpleScaledThread(flame, logDensityFilter, gammaCorrectionFilter, renderScale, startRow, endRow, pImage, newImg); threads.add(thread); if (threadCount > 1) { new Thread(thread).start(); } else { thread.run(); } } ThreadTools.waitForThreads(threadCount, threads); pImage.setBufferedImage(newImg.getBufferedImg(), newImg.getImageWidth(), newImg.getImageHeight()); } else if (renderScale == 1) { int rowsPerThread = pImage.getImageHeight() / threadCount; List<RenderImageSimpleThread> threads = new ArrayList<RenderImageSimpleThread>(); for (int i = 0; i < threadCount; i++) { int startRow = i * rowsPerThread; int endRow = i < rowsPerThread - 1 ? startRow + rowsPerThread : pImage.getImageHeight(); RenderImageSimpleThread thread = new RenderImageSimpleThread(flame, logDensityFilter, gammaCorrectionFilter, startRow, endRow, pImage); threads.add(thread); if (threadCount > 1) { new Thread(thread).start(); } else { thread.run(); } } ThreadTools.waitForThreads(threadCount, threads); } else { throw new IllegalArgumentException("renderScale " + renderScale); } } private AbstractRenderThread createFlameRenderThread(int pThreadId, int pThreadGroupSize, List<RenderPacket> pRenderPackets, long pSamples, List<RenderSlice> pSlices) { return new FlatRenderThread(prefs, pThreadId, pThreadGroupSize, this, pRenderPackets, pSamples, pSlices); } private void iterate(int pPart, int pParts, List<List<RenderPacket>> pPackets, List<RenderSlice> pSlices) { long nSamples = (long) ((flame.getSampleDensity() * (double) rasterSize / (double) flame.calcPostSymmetrySampleMultiplier() / (double) flame.calcStereo3dSampleMultiplier() / (double) (oversample) + 0.5)); int PROGRESS_STEPS = 21; if (progressUpdater != null && pPart == 0) { progressChangePerPhase = (PROGRESS_STEPS - 1) * pParts; progressUpdater.initProgress(progressChangePerPhase * progressDisplayPhaseCount); } long sampleProgressUpdateStep = nSamples / PROGRESS_STEPS; long nextProgressUpdate = sampleProgressUpdateStep; runningThreads = new ArrayList<AbstractRenderThread>(); int nThreads = pPackets.size(); for (int i = 0; i < nThreads; i++) { AbstractRenderThread t = createFlameRenderThread(i, nThreads, pPackets.get(i), nSamples / (long) nThreads, pSlices); runningThreads.add(t); new Thread(t).start(); } boolean done = false; while (!done) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } done = true; long currSamples = 0; for (AbstractRenderThread t : runningThreads) { if (!t.isFinished()) { done = false; } currSamples += t.getCurrSample(); } if (currSamples >= nextProgressUpdate) { if (progressUpdater != null) { int currProgress = (int) ((currSamples * PROGRESS_STEPS) / nSamples); progressUpdater.updateProgress(currProgress + pPart * PROGRESS_STEPS + progressChangePerPhase * progressDisplayPhase); nextProgressUpdate = (currProgress + 1) * sampleProgressUpdateStep; } } } } private RenderThreads startIterate(List<List<RenderPacket>> pFlames, RenderThreadPersistentState pState[], boolean pStartThreads) { List<AbstractRenderThread> renderThreads = new ArrayList<AbstractRenderThread>(); List<Thread> executingThreads = new ArrayList<Thread>(); int nThreads = pFlames.size(); for (int i = 0; i < nThreads; i++) { AbstractRenderThread t = createFlameRenderThread(i, nThreads, pFlames.get(i), -1, null); if (pState != null) { t.setResumeState(pState[i]); } t.setTonemapper(new SampleTonemapper(flame, raster, rasterWidth, rasterHeight, imageWidth, imageHeight, randGen)); renderThreads.add(t); if (pStartThreads) { Thread executingThread = new Thread(t); executingThreads.add(executingThread); executingThread.start(); } } return new RenderThreads(renderThreads, executingThreads); } public void setRandomNumberGenerator(AbstractRandomGenerator random) { this.randGen = random; } public void setProgressUpdater(ProgressUpdater pProgressUpdater) { progressUpdater = pProgressUpdater; } public void setRenderScale(int pRenderScale) { renderScale = pRenderScale; } protected List<IterationObserver> getIterationObservers() { return iterationObservers; } private void pauseThreads(List<AbstractRenderThread> pThreads) { while (true) { boolean done = true; for (AbstractRenderThread thread : pThreads) { if (!thread.isFinished()) { done = false; thread.cancel(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } break; } } if (done) { break; } } } public void saveState(String pAbsolutePath, List<AbstractRenderThread> pThreads, long pSampleCount, long pElapsedMilliseconds, QualityProfile pQualityProfile) { pauseThreads(pThreads); // store thread state RenderThreadPersistentState state[] = new RenderThreadPersistentState[pThreads.size()]; for (int i = 0; i < pThreads.size(); i++) { state[i] = pThreads.get(i).saveState(); } try { try { ObjectOutputStream outputStream = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(pAbsolutePath))); try { // save header JWFRenderFileHeader header = new JWFRenderFileHeader(pThreads.size(), flame.getWidth(), flame.getHeight(), pSampleCount, pElapsedMilliseconds, 1, 1, (int) (flame.getSampleDensity() + 0.5), true, false, withAlpha); outputStream.writeObject(header); // save flame outputStream.writeObject(flame); // save renderInfo outputStream.writeObject(renderInfo); // save thread state for (int i = 0; i < pThreads.size(); i++) { outputStream.writeObject(state[i]); } // save raster outputStream.writeObject(raster); } finally { outputStream.flush(); outputStream.close(); } } catch (Exception ex) { ex.printStackTrace(); try { new File(pAbsolutePath).delete(); } catch (Exception ex2) { } throw new RuntimeException(ex); } } finally { resumeThreads(pThreads, state); } } private void resumeThreads(List<AbstractRenderThread> pThreads, RenderThreadPersistentState pState[]) { for (int i = 0; i < pThreads.size(); i++) { AbstractRenderThread t = pThreads.get(i); t.setResumeState(pState[i]); new Thread(t).start(); } } private List<RenderPacket> createRenderPackets(Flame pFlame, int pFrame) { List<RenderPacket> res = new ArrayList<RenderPacket>(); { double time = pFrame >= 0 ? pFrame : 0; Flame newFlame = AnimationService.evalMotionCurves(pFlame.makeCopy(), time); for (Layer layer : newFlame.getLayers()) { layer.refreshModWeightTables(flameTransformationContext); } FlameRendererView view = createView(newFlame); res.add(new RenderPacket(newFlame, view)); } if (pFlame.getMotionBlurLength() > 0) { double time = pFrame >= 0 ? pFrame : 0; double currTime = time + pFlame.getMotionBlurLength() * pFlame.getMotionBlurTimeStep() / 2.0; for (int p = 1; p <= pFlame.getMotionBlurLength(); p++) { currTime -= pFlame.getMotionBlurTimeStep(); Flame newFlame = AnimationService.evalMotionCurves(pFlame.makeCopy(), currTime); for (Layer layer : newFlame.getLayers()) { layer.refreshModWeightTables(flameTransformationContext); double brightnessScl = (1.0 - p * p * pFlame.getMotionBlurDecay() * 0.07 / pFlame.getMotionBlurLength()); if (brightnessScl < 0.01) { brightnessScl = 0.01; } layer.setWeight(brightnessScl * layer.getWeight()); } FlameRendererView newView = createView(newFlame); res.add(new RenderPacket(newFlame, newView)); } } return res; } protected FlameRendererView createView(Flame initialFlame) { if (!Stereo3dEye.UNSPECIFIED.equals(eye)) { switch (initialFlame.getAnaglyph3dMode()) { case INTERPOLATED_IMAGES: case SIDE_BY_SIDE: case ANAGLYPH: return new Stereo3dFlameRendererView(eye, initialFlame, randGen, borderWidth, maxBorderWidth, imageWidth, imageHeight, rasterWidth, rasterHeight, flameTransformationContext); default: // nothing to do break; } } return new FlameRendererView(eye, initialFlame, randGen, borderWidth, maxBorderWidth, imageWidth, imageHeight, rasterWidth, rasterHeight, flameTransformationContext); } public RenderThreads startRenderFlame(RenderInfo pRenderInfo) { renderInfo = pRenderInfo; initRaster(pRenderInfo, pRenderInfo.getImageWidth(), pRenderInfo.getImageHeight(), flame.getSampleDensity()); List<List<RenderPacket>> renderFlames = new ArrayList<List<RenderPacket>>(); for (int t = 0; t < prefs.getTinaRenderThreads(); t++) { renderFlames.add(createRenderPackets(flame, flame.getFrame())); } return startIterate(renderFlames, null, true); } public ResumedFlameRender resumeRenderFlame(String pAbsolutePath) { try { ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(pAbsolutePath))); try { // read header JWFRenderFileHeader header = (JWFRenderFileHeader) in.readObject(); // read flame Flame rdFlame = (Flame) in.readObject(); flame.assign(rdFlame); // restore renderInfo renderInfo = (RenderInfo) in.readObject(); // restore thread state withAlpha = header.withTransparency; RenderThreadPersistentState state[] = new RenderThreadPersistentState[header.numThreads]; for (int i = 0; i < header.numThreads; i++) { state[i] = (RenderThreadPersistentState) in.readObject(); } initRaster(renderInfo, renderInfo.getImageWidth(), renderInfo.getImageHeight(), flame.getSampleDensity()); List<List<RenderPacket>> renderFlames = new ArrayList<List<RenderPacket>>(); for (int t = 0; t < header.numThreads; t++) { renderFlames.add(createRenderPackets(flame, flame.getFrame())); } raster = null; // read raster raster = (AbstractRaster) in.readObject(); // create threads RenderThreads threads = startIterate(renderFlames, state, false); return new ResumedFlameRender(header, threads.getRenderThreads()); } finally { in.close(); } } catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException(ex); } } public Flame getFlame() { return flame; } public RenderInfo getRenderInfo() { return renderInfo; } public void signalCancel() { forceAbort = true; } public void cancel() { forceAbort = true; if (runningThreads != null) { while (true) { boolean done = true; for (AbstractRenderThread thread : runningThreads) { if (!thread.isFinished()) { done = false; thread.cancel(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } break; } } if (done) { break; } } } } public boolean isPreview() { return preview; } public void setPreview(boolean pPreview) { preview = pPreview; } public void renderSlices(SliceRenderInfo pSliceRenderInfo, String pFilenamePattern) { if (!flame.isRenderable()) throw new RuntimeException("Slices can not be created of empty flames"); int fileIdx = 1; int passes = pSliceRenderInfo.getSlices() / pSliceRenderInfo.getSlicesPerRender(); if (pSliceRenderInfo.getSlices() % pSliceRenderInfo.getSlicesPerRender() != 0) passes++; progressDisplayPhaseCount = passes; double zmin = pSliceRenderInfo.getZmin() < pSliceRenderInfo.getZmax() ? pSliceRenderInfo.getZmin() : pSliceRenderInfo.getZmax(); double zmax = pSliceRenderInfo.getZmin() < pSliceRenderInfo.getZmax() ? pSliceRenderInfo.getZmax() : pSliceRenderInfo.getZmin(); double thickness = (zmax - zmin) / (double) pSliceRenderInfo.getSlices(); double currZ = zmax; int currSlice = 0; for (int pass = 0; pass < passes; pass++) { if (forceAbort) { break; } System.gc(); progressDisplayPhase = pass; Flame currFlame = flame.makeCopy(); prepareFlameForSliceRendering(currFlame); initRasterSizes(pSliceRenderInfo.getImageWidth(), pSliceRenderInfo.getImageHeight()); List<RenderSlice> slices = new ArrayList<RenderSlice>(); for (int i = 0; i < pSliceRenderInfo.getSlicesPerRender() && currSlice < pSliceRenderInfo.getSlices(); i++) { RenderSlice slice = new RenderSlice(allocRaster(flame.getSpatialOversampling(), flame.getSampleDensity()), currZ - thickness, currZ); slices.add(slice); currZ -= thickness; currSlice++; } List<List<RenderPacket>> renderFlames = new ArrayList<List<RenderPacket>>(); for (int t = 0; t < prefs.getTinaRenderThreads(); t++) { renderFlames.add(createRenderPackets(flame, flame.getFrame())); } iterate(0, 1, renderFlames, slices); if (!forceAbort) { LogDensityPoint logDensityPnt = new LogDensityPoint(flame.getActiveLightCount()); while (slices.size() > 0) { if (forceAbort) { break; } RenderSlice slice = slices.get(0); RenderedFlame renderedFlame = new RenderedFlame(); renderedFlame.init(pSliceRenderInfo.createRenderInfo(), flame); SimpleImage img = renderedFlame.getImage(); logDensityFilter.setRaster(slice.getRaster(), rasterWidth, rasterHeight, img.getImageWidth(), img.getImageHeight()); GammaCorrectedRGBPoint rbgPoint = new GammaCorrectedRGBPoint(); for (int i = 0; i < img.getImageHeight(); i++) { for (int j = 0; j < img.getImageWidth(); j++) { logDensityFilter.transformPoint(logDensityPnt, j, i); gammaCorrectionFilter.transformPoint(logDensityPnt, rbgPoint, j, i); img.setARGB(j, i, rbgPoint.alpha, rbgPoint.red, rbgPoint.green, rbgPoint.blue); } } String filename = String.format(pFilenamePattern, fileIdx++); try { new ImageWriter().saveImage(renderedFlame.getImage(), filename); } catch (Exception ex) { throw new RuntimeException(ex); } slice = null; slices.remove(0); } } } } private void prepareFlameForSliceRendering(Flame pFlame) { pFlame.setStereo3dMode(Stereo3dMode.NONE); pFlame.setDimishZ(0.0); pFlame.setCamDOF(0.0); pFlame.setCamPerspective(0.0); } protected AbstractRaster getRaster() { return raster; } }