mattercontrol/MatterControlLib/DesignTools/Lithophane.cs
2021-03-10 09:53:41 -08:00

199 lines
5.4 KiB
C#

/*
Copyright (c) 2018, John Lewin
*/
using System;
using System.Diagnostics;
using System.Linq;
using MatterHackers.Agg;
using MatterHackers.Agg.Image;
using MatterHackers.PolygonMesh;
using MatterHackers.VectorMath;
namespace MatterHackers.MatterControl.Plugins.Lithophane
{
public static class Lithophane
{
class PixelInfo
{
public Vector3 Top { get; set; }
public Vector3 Bottom { get; set; }
}
public static Mesh Generate(IImageData resizedImage, double maxZ, double nozzleWidth, double pixelsPerMM, bool invert, IProgress<ProgressStatus> reporter)
{
// TODO: Move this to a user supplied value
double baseThickness = nozzleWidth; // base thickness (in mm)
double zRange = maxZ - baseThickness;
// Dimensions of image
var width = resizedImage.Width;
var height = resizedImage.Height;
var zScale = zRange / 255;
var pixelData = resizedImage.Pixels;
Stopwatch stopwatch = Stopwatch.StartNew();
var mesh = new Mesh();
//var rescale = (double)onPlateWidth / imageData.Width;
var rescale = 1;
var progressStatus = new ProgressStatus();
// Build an array of PixelInfo objects from each pixel
// Collapse from 4 bytes per pixel to one - makes subsequent processing more logical and has minimal cost
var pixels = pixelData.Where((x, i) => i % 4 == 0)
// Interpolate the pixel color to zheight
.Select(b => baseThickness + (invert ? 255 - b : b) * zScale)
// Project to Vector3 for each pixel at the computed x/y/z
.Select((z, i) => new Vector3(
i % width * rescale,
(i - i % width) / width * rescale * -1,
z))
// Project to PixelInfo, creating a mirrored Vector3 at z0, paired together and added to the mesh
.Select(vec =>
{
var pixelInfo = new PixelInfo()
{
Top = vec,
Bottom = new Vector3(vec.X, vec.Y, 0)
};
mesh.Vertices.Add(pixelInfo.Top);
mesh.Vertices.Add(pixelInfo.Bottom);
return pixelInfo;
}).ToArray();
Console.WriteLine("ElapsedTime - PixelInfo Linq Generation: {0}", stopwatch.ElapsedMilliseconds);
stopwatch.Restart();
// Select pixels along image edges
var backRow = pixels.Take(width).Reverse().ToArray();
var frontRow = pixels.Skip((height - 1) * width).Take(width).ToArray();
var leftRow = pixels.Where((x, i) => i % width == 0).ToArray();
var rightRow = pixels.Where((x, i) => (i + 1) % width == 0).Reverse().ToArray();
int k,
nextJ,
nextK;
var notificationInterval = 100;
var workCount = (resizedImage.Width - 1) * (resizedImage.Height - 1) +
(height - 1) +
(width - 1);
double workIndex = 0;
// Vertical faces: process each row and column, creating the top and bottom faces as appropriate
for (int i = 0; i < resizedImage.Height - 1; ++i)
{
var startAt = i * width;
// Process each column
for (int j = startAt; j < startAt + resizedImage.Width - 1; ++j)
{
k = j + 1;
nextJ = j + resizedImage.Width;
nextK = nextJ + 1;
// Create north, then south face
mesh.CreateFace(new [] { pixels[k].Top, pixels[j].Top, pixels[nextJ].Top, pixels[nextK].Top });
mesh.CreateFace(new [] { pixels[j].Bottom, pixels[k].Bottom, pixels[nextK].Bottom, pixels[nextJ].Bottom });
workIndex++;
if (workIndex % notificationInterval == 0)
{
progressStatus.Progress0To1 = workIndex / workCount;
reporter.Report(progressStatus);
}
}
}
// Side faces: East/West
for (int j = 0; j < height - 1; ++j)
{
//Next row
k = j + 1;
// Create east, then west face
mesh.CreateFace(new [] { leftRow[k].Top, leftRow[j].Top, leftRow[j].Bottom, leftRow[k].Bottom });
mesh.CreateFace(new [] { rightRow[k].Top, rightRow[j].Top, rightRow[j].Bottom, rightRow[k].Bottom });
workIndex++;
if (workIndex % notificationInterval == 0)
{
progressStatus.Progress0To1 = workIndex / workCount;
reporter.Report(progressStatus);
}
}
// Side faces: North/South
for (int j = 0; j < width - 1; ++j)
{
// Next row
k = j + 1;
// Create north, then south face
mesh.CreateFace(new [] { frontRow[k].Top, frontRow[j].Top, frontRow[j].Bottom, frontRow[k].Bottom });
mesh.CreateFace(new [] { backRow[k].Top, backRow[j].Top, backRow[j].Bottom, backRow[k].Bottom });
workIndex++;
if (workIndex % notificationInterval == 0)
{
progressStatus.Progress0To1 = workIndex / workCount;
reporter.Report(progressStatus);
}
}
Console.WriteLine("ElapsedTime - Face Generation: {0}", stopwatch.ElapsedMilliseconds);
return mesh;
}
public interface IImageData
{
byte[] Pixels { get; }
int Width { get; }
int Height { get; }
}
public class ImageBufferImageData : IImageData
{
ImageBuffer resizedImage;
public ImageBufferImageData(ImageBuffer image, double pixelWidth)
{
resizedImage = this.ToResizedGrayscale(image, pixelWidth).MirrorY();
}
public int Width => resizedImage.Width;
public int Height => resizedImage.Height;
private ImageBuffer ToResizedGrayscale(ImageBuffer image, double onPlateWidth = 0)
{
var ratio = onPlateWidth / image.Width;
var resizedImage = image.CreateScaledImage(ratio);
var grayImage = resizedImage.ToGrayscale();
// Render grayscale pixels onto resized image with larger pixel format needed by caller
resizedImage.NewGraphics2D().Render(grayImage, 0, 0);
return resizedImage;
}
public byte[] Pixels => resizedImage.GetBuffer();
}
}
}