/// <summary>Computes the Discrete Fourier Transform of a double2 vector x whose length is a power of 16. /// x = { Re[x0] Im[x0] Re[x1] Im[x1] ... Re[xn] Im[xn] }, n = power of 16 (Length = 2*pow(16,n))</summary> public static CLCalc.Program.Variable FFT16(ref CLCalc.Program.Variable CLx) { if (CLy == null || CLy.OriginalVarLength != CLx.OriginalVarLength) { CLy = new CLCalc.Program.Variable(new float[CLx.OriginalVarLength]); } if (CLCalc.CLAcceleration != CLCalc.CLAccelerationType.UsingCL) { return(null); } int nn = (int)Math.Log(CLx.OriginalVarLength >> 1, 16); nn = 1 << ((nn << 2) + 1); if (nn != CLx.OriginalVarLength) { throw new Exception("Number of elements should be a power of 16 ( vector length should be 2*pow(16,n) )"); } if (kernelfft_radix16 == null) { InitKernels(); } CLCalc.Program.Variable[] args = new CLCalc.Program.Variable[] { CLx, CLy, CLp }; CLCalc.Program.Variable[] args2 = new CLCalc.Program.Variable[] { CLy, CLx, CLp }; bool usar2 = true; int[] p = new int[] { 1 }; CLp.WriteToDevice(p); int n = CLx.OriginalVarLength >> 5; while (p[0] <= n) { usar2 = !usar2; if (usar2) { kernelfft_radix16.Execute(args2, n); } else { kernelfft_radix16.Execute(args, n); } p[0] = p[0] << 4; CLp.WriteToDevice(p); } if (usar2) { CLCalc.Program.Variable temp = CLx; CLx = CLy; CLy = temp; } return(CLy); }
private CLCalc.Program.Variable CLCalcVertexDisplacement(float[] ObjVertex, float[] ObjDisplacement) { CLCalc.Program.Variable variable1 = new CLCalc.Program.Variable(ObjVertex); CLCalc.Program.Variable variable2 = new CLCalc.Program.Variable(ObjDisplacement); CLCalc.Program.Variable variable3 = new CLCalc.Program.Variable(ObjVertex); float[] numArray = new float[9]; CLCalc.Program.Variable variable4 = new CLCalc.Program.Variable(numArray); int[] GlobalWorkSize = new int[1] { ObjVertex.Length }; if ((double)ObjDisplacement[3] != 0.0 || (double)ObjDisplacement[4] != 0.0 || (double)ObjDisplacement[5] != 0.0) { this.CalcRotMatrix(numArray, ObjDisplacement); variable4.WriteToDevice(numArray); this.kernelCalcRotacoes.Execute((CLCalc.Program.MemoryObject[]) new CLCalc.Program.Variable[3] { variable4, variable1, variable3 }, GlobalWorkSize); } this.kernelCalcTransl.Execute((CLCalc.Program.MemoryObject[]) new CLCalc.Program.Variable[2] { variable2, variable3 }, GlobalWorkSize); return(variable3); }
/// <summary>Computes the Discrete Fourier Transform of a double2 vector x whose length is a power of 16. /// x = { Re[x0] Im[x0] Re[x1] Im[x1] ... Re[xn] Im[xn] }, n = power of 16 (Length = 2*pow(16,n))</summary> public static double[] FFT16(double[] x) { if (CLx == null || CLx.OriginalVarLength != x.Length) { CLx = new CLCalc.Program.Variable(x); CLy = new CLCalc.Program.Variable(x); } //Writes original content CLx.WriteToDevice(x); CLy = FFT16(ref CLx); double[] y = new double[x.Length]; CLy.ReadFromDeviceTo(y); return(y); }
/// <summary>Computes the inverse Discrete Fourier Transform of a double2 vector x whose length is a power of 16. /// x = { Re[x0] Im[x0] Re[x1] Im[x1] ... Re[xn] Im[xn] }, n = power of 16 (Length = 2*pow(16,n))</summary> public static CLCalc.Program.Variable iFFT16(CLCalc.Program.Variable CLx) { if (CLScale == null) { CLScale = new CLCalc.Program.Variable(new double[1]); } //Trick: DFT-1 (x) = DFT(x*)*/N; //Conjugate double[] vx = new double[CLx.OriginalVarLength]; CLx.ReadFromDeviceTo(vx); double[] scale = new double[] { 1 }; CLScale.WriteToDevice(scale); kernelConjugate.Execute(new CLCalc.Program.Variable[] { CLx, CLScale }, CLx.OriginalVarLength >> 1); CLx.ReadFromDeviceTo(vx); CLy = FFT16(ref CLx); scale[0] = 1 / (double)(CLx.OriginalVarLength >> 1); CLScale.WriteToDevice(scale); kernelConjugate.Execute(new CLCalc.Program.Variable[] { CLy, CLScale }, CLy.OriginalVarLength >> 1); return(CLy); }
public int Step(int nSteps) { //grabs the Stereoscopic parameters dimensions[0] = CLX.VarSize; dimensions[1] = CLY.VarSize; dimensions[2] = CLZ.VarSize; CLDimensions.WriteToDevice(dimensions); //steps forward; for (int k = 0; k < nSteps - 1; k++) { vetTransl.Execute(new CLCalc.Program.MemoryObject[] { CLimg, CLDimensions, CLX, CLY, CLZ }, new int[] { dimensions[0] - 2, dimensions[1] - 2 }); } vetTransl.Execute(new CLCalc.Program.MemoryObject[] { CLimg, CLDimensions, CLX, CLY, CLZ }, new int[] { dimensions[0] - 2, dimensions[1] - 2 }); return(nSteps << 1); }
/// <summary>Equalizes image histogram using OpenCL</summary> private void CLEqualizeHistogram(ref Bitmap bmp) { if (CLCalc.CLAcceleration == CLCalc.CLAccelerationType.Unknown) { CLCalc.InitCL(); } if (CLCalc.CLAcceleration != CLCalc.CLAccelerationType.UsingCL) { return; } float[] PartialHistograms = new float[NLumIntens * bmp.Width]; float[] histLuminance = new float[NLumIntens]; if (kernelComputeHistograms == null || CLN == null || CLHistogram == null) { CLHistogram = new CLCalc.Program.Variable(histLuminance); CLPartialHistograms = new CLCalc.Program.Variable(PartialHistograms); } InitKernels(); System.Diagnostics.Stopwatch swTotal = new System.Diagnostics.Stopwatch(); System.Diagnostics.Stopwatch swCopyBmp = new System.Diagnostics.Stopwatch(); System.Diagnostics.Stopwatch swRescaling = new System.Diagnostics.Stopwatch(); System.Diagnostics.Stopwatch swComputeHistPartial = new System.Diagnostics.Stopwatch(); System.Diagnostics.Stopwatch swComputeHistConsolid = new System.Diagnostics.Stopwatch(); System.Diagnostics.Stopwatch swHistIntegral = new System.Diagnostics.Stopwatch(); swTotal.Start(); swCopyBmp.Start(); if (CLbmp == null || CLbmp.Height != bmp.Height || CLbmp.Width != bmp.Width) { CLbmp = new CLCalc.Program.Image2D(bmp); CLNewBmp = new CLCalc.Program.Image2D(bmp); CLPartialHistograms = new CLCalc.Program.Variable(PartialHistograms); } else { CLbmp.WriteBitmap(bmp); CLN.WriteToDevice(new int[] { NLumIntens }); CLWidth.WriteToDevice(new int[] { bmp.Width }); CLHeight.WriteToDevice(new int[] { bmp.Height }); } swCopyBmp.Stop(); swComputeHistPartial.Start(); //Partial histograms CLCalc.Program.MemoryObject[] args = new CLCalc.Program.MemoryObject[] { CLbmp, CLPartialHistograms, CLHeight, CLN }; kernelComputeHistograms.Execute(args, bmp.Width); CLCalc.Program.Sync(); swComputeHistPartial.Stop(); swComputeHistConsolid.Start(); args = new CLCalc.Program.MemoryObject[] { CLPartialHistograms, CLHistogram, CLHeight, CLN }; kernelConsolidateHist.Execute(args, NLumIntens); CLHistogram.ReadFromDeviceTo(histLuminance); swComputeHistConsolid.Stop(); swHistIntegral.Start(); //Perform histogram integration - better performance in CPU //Compute histogram integrals in-place for (int i = 1; i < NLumIntens; i++) { histLuminance[i] += histLuminance[i - 1]; } float scale = 0.9f / histLuminance[NLumIntens - 1]; //Scales histograms for (int i = 0; i < NLumIntens; i++) { histLuminance[i] *= scale; } //Writes histogram integral CLHistogram.WriteToDevice(histLuminance); swHistIntegral.Stop(); swRescaling.Start(); //Computes equalized image args = new CLCalc.Program.MemoryObject[] { CLbmp, CLNewBmp, CLHistogram, CLN }; kernelPerformNormalization.Execute(args, new int [] { bmp.Width, bmp.Height }); bmp = CLNewBmp.ReadBitmap(); swRescaling.Stop(); swTotal.Stop(); }
/// <summary>Creates a new isosurface calculator. You may pass variables created from a OpenGL context to the CL variables if you are using interop or NULL /// if not using OpenCL/GL interop.</summary> /// <param name="FuncValues">Values of the evaluated 3D function f(x,y,z). FuncValues=float[maxX,maxY,maxZ]</param> /// <param name="CLEdgeCoords">OpenCL variable (float) to hold edge coordinates. Dimension has to be 9 * maxX * maxY * maxZ</param> /// <param name="CLEdgeNormals">OpenCL variable (float) to hold edge normals. Dimension has to be 9 * maxX * maxY * maxZ</param> /// <param name="CLElementArrayIndex">OpenCL variable (int) to hold element array index. Dimension has to be 5 * 3 * (maxX - 1) * (maxY - 1) * (maxZ - 1)</param> private void InitMarchingCubes(float[, ,] FuncValues, CLCalc.Program.Variable CLEdgeCoords, CLCalc.Program.Variable CLEdgeNormals, CLCalc.Program.Variable CLElementArrayIndex) { if (CLCalc.CLAcceleration == CLCalc.CLAccelerationType.Unknown) CLCalc.InitCL(); if (CLCalc.CLAcceleration == CLCalc.CLAccelerationType.UsingCL) { //Reads maximum lengths int maxX = FuncValues.GetLength(0); int maxY = FuncValues.GetLength(1); int maxZ = FuncValues.GetLength(2); max = new int[] { maxX, maxY, maxZ }; #region Creating variables //Isolevel isoLevel = new float[1] { 1.32746E-5f }; varIsoLevel = new CLCalc.Program.Variable(isoLevel); //Step size and x0,y0,z0 varStep = new CLCalc.Program.Variable(step); varInitVals = new CLCalc.Program.Variable(initVals); //Create and copy function values funcVals = new float[maxX * maxY * maxZ]; CLFuncVals = new CLCalc.Program.Variable(funcVals); SetFuncVals(FuncValues); //Edge coordinates - 3 coords * 3 possible directions * number of points edgeCoords = new float[9 * maxX * maxY * maxZ]; if (CLEdgeCoords != null) { varEdgeCoords = CLEdgeCoords; varEdgeCoords.WriteToDevice(edgeCoords); } else varEdgeCoords = new CLCalc.Program.Variable(edgeCoords); //4 preliminary normals per edge - has to be averaged afterwards edgePrelimNormals = new float[36 * maxX * maxY * maxZ]; varEdgePrelimNormals = new CLCalc.Program.Variable(edgePrelimNormals); //Edge normals edgeNormals = new float[9 * maxX * maxY * maxZ]; if (CLEdgeNormals != null) { varEdgeNormals = CLEdgeNormals; varEdgeNormals.WriteToDevice(edgeNormals); } else varEdgeNormals = new CLCalc.Program.Variable(edgeNormals); //Number of cubes: (maxX-1)*(maxY-1)*(maxZ-1) //Marching cube algorithm: each cube can have 5 triangles drawn, 3 vertexes per triangle //q-th vertex of p-th triangle of the ijk-th cube: [(5*(i+(maxX-1)*j+k*(maxX-1)*(maxY-1))+p)*3+q] elementIndex = new int[5 * 3 * (maxX - 1) * (maxY - 1) * (maxZ - 1)]; if (CLElementArrayIndex != null) { varElemIndex = CLElementArrayIndex; varElemIndex.WriteToDevice(elementIndex); } else varElemIndex = new CLCalc.Program.Variable(elementIndex); //Edge remapping to build output edges = new int[edgeCoords.Length / 3]; for (int i = 0; i < edges.Length; i++) edges[i] = -1; #endregion #region Compile code and create kernels CLMarchingCubesSrc cmsrc = new CLMarchingCubesSrc(); CLCalc.Program.Compile(new string[] { cmsrc.definitions, cmsrc.src }); kernelInterpPts = new CLCalc.Program.Kernel("interpPts"); kernelPolygonize = new CLCalc.Program.Kernel("Polygonize"); kernelSmoothNormals = new CLCalc.Program.Kernel("SmoothNormals"); kernelPolygonizeNoNormals = new CLCalc.Program.Kernel("PolygonizeNoNormals"); #endregion } else throw new Exception("OpenCL not available"); }
/// <summary>Creates a new isosurface calculator. You may pass variables created from a OpenGL context to the CL variables if you are using interop or NULL /// if not using OpenCL/GL interop.</summary> /// <param name="FuncValues">Values of the evaluated 3D function f(x,y,z). FuncValues=float[maxX,maxY,maxZ]</param> /// <param name="CLEdgeCoords">OpenCL variable (float) to hold edge coordinates. Dimension has to be 9 * maxX * maxY * maxZ</param> /// <param name="CLEdgeNormals">OpenCL variable (float) to hold edge normals. Dimension has to be 9 * maxX * maxY * maxZ</param> /// <param name="CLElementArrayIndex">OpenCL variable (int) to hold element array index. Dimension has to be 5 * 3 * (maxX - 1) * (maxY - 1) * (maxZ - 1)</param> private void InitMarchingCubes(float[, ,] FuncValues, CLCalc.Program.Variable CLEdgeCoords, CLCalc.Program.Variable CLEdgeNormals, CLCalc.Program.Variable CLElementArrayIndex) { if (CLCalc.CLAcceleration == CLCalc.CLAccelerationType.Unknown) { CLCalc.InitCL(); } if (CLCalc.CLAcceleration == CLCalc.CLAccelerationType.UsingCL) { //Reads maximum lengths int maxX = FuncValues.GetLength(0); int maxY = FuncValues.GetLength(1); int maxZ = FuncValues.GetLength(2); max = new int[] { maxX, maxY, maxZ }; #region Creating variables //Isolevel isoLevel = new float[1] { 1.32746E-5f }; varIsoLevel = new CLCalc.Program.Variable(isoLevel); //Step size and x0,y0,z0 varStep = new CLCalc.Program.Variable(step); varInitVals = new CLCalc.Program.Variable(initVals); //Create and copy function values funcVals = new float[maxX * maxY * maxZ]; CLFuncVals = new CLCalc.Program.Variable(funcVals); SetFuncVals(FuncValues); //Edge coordinates - 3 coords * 3 possible directions * number of points edgeCoords = new float[9 * maxX * maxY * maxZ]; if (CLEdgeCoords != null) { varEdgeCoords = CLEdgeCoords; varEdgeCoords.WriteToDevice(edgeCoords); } else { varEdgeCoords = new CLCalc.Program.Variable(edgeCoords); } //4 preliminary normals per edge - has to be averaged afterwards edgePrelimNormals = new float[36 * maxX * maxY * maxZ]; varEdgePrelimNormals = new CLCalc.Program.Variable(edgePrelimNormals); //Edge normals edgeNormals = new float[9 * maxX * maxY * maxZ]; if (CLEdgeNormals != null) { varEdgeNormals = CLEdgeNormals; varEdgeNormals.WriteToDevice(edgeNormals); } else { varEdgeNormals = new CLCalc.Program.Variable(edgeNormals); } //Number of cubes: (maxX-1)*(maxY-1)*(maxZ-1) //Marching cube algorithm: each cube can have 5 triangles drawn, 3 vertexes per triangle //q-th vertex of p-th triangle of the ijk-th cube: [(5*(i+(maxX-1)*j+k*(maxX-1)*(maxY-1))+p)*3+q] elementIndex = new int[5 * 3 * (maxX - 1) * (maxY - 1) * (maxZ - 1)]; if (CLElementArrayIndex != null) { varElemIndex = CLElementArrayIndex; varElemIndex.WriteToDevice(elementIndex); } else { varElemIndex = new CLCalc.Program.Variable(elementIndex); } //Edge remapping to build output edges = new int[edgeCoords.Length / 3]; for (int i = 0; i < edges.Length; i++) { edges[i] = -1; } #endregion #region Compile code and create kernels CLMarchingCubesSrc cmsrc = new CLMarchingCubesSrc(); CLCalc.Program.Compile(new string[] { cmsrc.definitions, cmsrc.src }); kernelInterpPts = new CLCalc.Program.Kernel("interpPts"); kernelPolygonize = new CLCalc.Program.Kernel("Polygonize"); kernelSmoothNormals = new CLCalc.Program.Kernel("SmoothNormals"); kernelPolygonizeNoNormals = new CLCalc.Program.Kernel("PolygonizeNoNormals"); #endregion } else { throw new Exception("OpenCL not available"); } }
/// <summary>Reads data from a bitmap.</summary> /// <param name="bmp">Source bitmap</param> public void ReadBmp(Bitmap bmp) { ReadToLocalData(bmp); varData.WriteToDevice(Data); }
/// <summary>Writes Training Set into device memory</summary> private void WriteToDevice() { //Vector length if (HostVLen == null) { HostVLen = new int[1]; HostVLen[0] = (1 + ((TrainingSet.trainingArray[0].xVector.Length - 1) >> 2)) << 2; } //Populates OpenCL buffer if (TrainingFeatures == null) // || TrainingFeatures.Length != TrainingSet.getN * HostVLen[0]) { TrainingFeatures = new float[TrainingSet.getN * HostVLen[0]]; } for (int i = 0; i < TrainingSet.getN; i++) { for (int j = 0; j < TrainingSet.trainingArray[0].xVector.Length; j++) { TrainingFeatures[j + i * HostVLen[0]] = TrainingSet.trainingArray[i].xVector[j]; } } if (CLCalc.CLAcceleration != CLCalc.CLAccelerationType.UsingCL) { return; } lock (CLResource) { //Writes to OpenCL memory //if (CLTrainingFeatures != null) CLTrainingFeatures.Dispose(); if (CLTrainingFeatures == null || CLTrainingFeatures.OriginalVarLength != TrainingFeatures.Length) { if (CLTrainingFeatures != null) { CLTrainingFeatures.Dispose(); } CLTrainingFeatures = new CLCalc.Program.Variable(TrainingFeatures); } else { CLTrainingFeatures.WriteToDevice(TrainingFeatures); } //if (CLXVecLen != null) CLXVecLen.Dispose(); if (CLXVecLen == null) { CLXVecLen = new CLCalc.Program.Variable(HostVLen); } else { CLXVecLen.WriteToDevice(HostVLen); } HostSample = new float[HostVLen[0]]; //if (CLSample != null) CLSample.Dispose(); if (CLSample == null) { CLSample = new CLCalc.Program.Image2D(HostSample, HostVLen[0] >> 2, 1); } else { CLSample.WriteToDevice(HostSample); } //if (CLKernelValues != null) CLKernelValues.Dispose(); if (CLKernelValues == null || CLKernelValues.OriginalVarLength != TrainingSet.getN) { CLKernelValues = new CLCalc.Program.Variable(new float[TrainingSet.getN]); } else { CLKernelValues.WriteToDevice(new float[TrainingSet.getN]); } //if (CLLambda != null) CLLambda.Dispose(); if (CLLambda == null) { CLLambda = new CLCalc.Program.Variable(new float[] { this.ProblemCfg.lambda }); } else { CLLambda.WriteToDevice(new float[] { this.ProblemCfg.lambda }); } } }
/// <summary>Shades a bitmap</summary> /// <param name="bmp">Bitmap to shade</param> /// <param name="bmp">Actually render?</param> /// <returns></returns> public void Shade(Bitmap bmp, PictureBox pb, bool doRender) { Bitmap bmp2 = new Bitmap(bmp.Width, bmp.Height); Graphics g = Graphics.FromImage(bmp2); g.DrawImage(bmp, 0, 0, bmp.Width, bmp.Height); //draws points onto thresholded bitmap for (int i = 0; i < Colors.Count; i++) { if (Points[i].Count > 1) { g.DrawLines(new Pen(Color.Black, 2), Points[i].ToArray()); } } #region Initializations if (CLbmpSrc == null || CLbmpSrc.Width != bmp.Width || CLbmpSrc.Height != bmp.Height) { CLbmpSrc = new CLCalc.Program.Image2D(bmp2); CLbmpThresh = new CLCalc.Program.Image2D(bmp); CLthreshInfo = new CLCalc.Program.Variable(typeof(byte), bmp.Width * bmp.Height); CLbmpStroke = new CLCalc.Program.Image2D(bmp); CLbmpDists = new CLCalc.Program.Image2D(bmp); CLimgFinal1 = new CLCalc.Program.Image2D(new Bitmap(bmp2.Width, bmp2.Height)); CLimgFinal2 = new CLCalc.Program.Image2D(new Bitmap(bmp2.Width, bmp2.Height)); CLTotalPixWeight = new CLCalc.Program.Variable(typeof(float), bmp.Width * bmp.Height); lastPixWeight = new float[bmp.Width * bmp.Height]; CLWeight = new CLCalc.Program.Variable(typeof(float), bmp.Width * bmp.Height); lastImage = new Bitmap(bmp.Width, bmp.Height); } else { CLbmpSrc.WriteBitmap(bmp2); CLimgFinal1.WriteBitmap(new Bitmap(bmp2.Width, bmp2.Height)); CLimgFinal2.WriteBitmap(new Bitmap(bmp2.Width, bmp2.Height)); kernelinitTotalWeight.Execute(new CLCalc.Program.MemoryObject[] { CLTotalPixWeight }, new int[] { CLbmpSrc.Width, CLbmpSrc.Height }); } #endregion System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); System.Diagnostics.Stopwatch swCL = new System.Diagnostics.Stopwatch(); sw.Start(); //computes threshold kernelThreshold.Execute(new CLCalc.Program.MemoryObject[] { CLcfgVars, CLbmpSrc, CLthreshInfo, CLbmpThresh }, new int[] { bmp.Width, bmp.Height }); if (!doRender) { return; } //Reuse last render? if (ReUseLastRender) { CLTotalPixWeight.WriteToDevice(lastPixWeight); CLimgFinal1.WriteBitmap(lastImage); } else { lastRenderedIdx = 0; } //generates images for points for (int i = lastRenderedIdx; i < Colors.Count; i++) { Bitmap bmpStroke = new Bitmap(bmp.Width, bmp.Height); if (Points[i].Count > 1) { if (DistanceMaps[i] == null) { Graphics g2 = Graphics.FromImage(bmpStroke); g2.DrawLines(new Pen(Colors[i], 2), Points[i].ToArray()); CLbmpStroke.WriteBitmap(bmpStroke); int[] changed = new int[] { 0 }; swCL.Start(); changed[0] = 1; kernelinitWeight.Execute(new CLCalc.Program.MemoryObject[] { CLWeight }, new int[] { CLbmpSrc.Width, CLbmpSrc.Height }); int n = 0; while (changed[0] > 0 && n < MAXPROPAGITERS) { n++; changed[0] = 0; CLchanged.WriteToDevice(changed); kernelPropagateDist.Execute(new CLCalc.Program.MemoryObject[] { CLchanged, CLbmpStroke, CLthreshInfo, CLWeight, CLbmpDists }, new int[] { CLbmpSrc.Width, CLbmpSrc.Height }); CLchanged.ReadFromDeviceTo(changed); } swCL.Stop(); DistanceMaps[i] = new float[CLWeight.OriginalVarLength]; CLWeight.ReadFromDeviceTo(DistanceMaps[i]); } else { CLWeight.WriteToDevice(DistanceMaps[i]); } //composes total weight CLcolor.WriteToDevice(new float[] { (float)Colors[i].B, (float)Colors[i].G, (float)Colors[i].R }); kernelAddToTotalWeight.Execute(new CLCalc.Program.MemoryObject[] { CLTotalPixWeight, CLWeight, CLcolor, CLimgFinal1, CLimgFinal2, CLthreshInfo }, new int[] { CLbmpSrc.Width, CLbmpSrc.Height }); //swaps final images - result is always on CLimgFinal1 CLCalc.Program.Image2D temp = CLimgFinal1; CLimgFinal1 = CLimgFinal2; CLimgFinal2 = temp; if (pb != null) { pb.Image = GetRenderedImage(); pb.Refresh(); } } bmpStroke.Dispose(); } sw.Stop(); lastRenderedIdx = Colors.Count; //saves total pixel weight and final image CLTotalPixWeight.ReadFromDeviceTo(lastPixWeight); if (lastImage != null) { lastImage.Dispose(); } lastImage = CLimgFinal1.ReadBitmap(); }