diff --git a/Aaru.Core/Aaru.Core.csproj b/Aaru.Core/Aaru.Core.csproj index bb4fbd602..e77a7a5b6 100644 --- a/Aaru.Core/Aaru.Core.csproj +++ b/Aaru.Core/Aaru.Core.csproj @@ -79,6 +79,7 @@ + @@ -164,6 +165,7 @@ + diff --git a/Aaru.Core/Graphics/Spiral.cs b/Aaru.Core/Graphics/Spiral.cs new file mode 100644 index 000000000..6921e6dc1 --- /dev/null +++ b/Aaru.Core/Graphics/Spiral.cs @@ -0,0 +1,490 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : DataFile.cs +// Author(s) : Natalia Portillo +// +// Component : Core algorithms. +// +// --[ Description ] ---------------------------------------------------------- +// +// Abstracts writing to files. +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program 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 General public License for more details. +// +// You should have received a copy of the GNU General public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2023 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using Aaru.CommonTypes; +using SkiaSharp; + +namespace Aaru.Core.Graphics; + +public sealed class Spiral +{ + static readonly DiscParameters _cdParameters = new(120, 15, 33, 46, 50, 116, 0, 0, 360000, SKColors.Silver); + static readonly DiscParameters _cdRecordableParameters = + new(120, 15, 33, 46, 50, 116, 45, 46, 360000, new SKColor(0xBD, 0xA0, 0x00)); + static readonly DiscParameters _cdRewritableParameters = + new(120, 15, 33, 46, 50, 116, 45, 46, 360000, new SKColor(0x50, 0x50, 0x50)); + static readonly DiscParameters _dvdPlusRParameters = + new(120, 15, 33, 46.8f, 48, 116, 46.586f, 46.8f, 2295104, new SKColor(0x6f, 0x0A, 0xCA)); + static readonly DiscParameters _dvdPlusRParameters80 = + new(80, 15, 33, 46.8f, 48, 76, 46.586f, 46.8f, 714544, new SKColor(0x6f, 0x0A, 0xCA)); + static readonly DiscParameters _dvdPlusRwParameters = + new(120, 15, 33, 44, 48, 116, 47.792f, 48, 2295104, new SKColor(0x38, 0x38, 0x38)); + static readonly DiscParameters _dvdPlusRwParameters80 = + new(80, 15, 33, 44, 48, 76, 47.792f, 48, 714544, new SKColor(0x38, 0x38, 0x38)); + static readonly DiscParameters _ps1CdParameters = new(120, 15, 33, 46, 50, 116, 0, 0, 360000, SKColors.Black); + static readonly DiscParameters _ps2CdParameters = + new(120, 15, 33, 46, 50, 116, 0, 0, 360000, new SKColor(0x0c, 0x08, 0xc3)); + static readonly DiscParameters _dvdParameters = + new(120, 15, 33, 44, 48, 116, 0, 0, 2294922, new SKColor(0x6f, 0x0A, 0xCA)); + static readonly DiscParameters _dvdParameters80 = + new(120, 15, 33, 44, 48, 76, 0, 0, 714544, new SKColor(0x6f, 0x0A, 0xCA)); + static readonly DiscParameters _dvdRParameters = + new(120, 15, 33, 46, 48, 116, 44, 46, 2294922, new SKColor(0x6f, 0x0A, 0xCA)); + static readonly DiscParameters _dvdRParameters80 = + new(80, 15, 33, 46, 48, 76, 44, 46, 712891, new SKColor(0x6f, 0x0A, 0xCA)); + static readonly DiscParameters _dvdRwParameters = + new(120, 15, 33, 46, 48, 116, 44, 46, 2294922, new SKColor(0x38, 0x38, 0x38)); + static readonly DiscParameters _dvdRwParameters80 = + new(80, 15, 33, 46, 48, 76, 44, 46, 712891, new SKColor(0x38, 0x38, 0x38)); + readonly SKCanvas _canvas; + readonly List _leadInPoints; + readonly long _maxSector; + readonly List _points; + readonly List _recordableInformationPoints; + + /// Initializes a spiral + /// Width in pixels for the underlying bitmap + /// Height in pixels for the underlying bitmap + /// Disc parameters + /// Last sector that will be drawn into the spiral + public Spiral(int width, int height, DiscParameters parameters, ulong lastSector) + { + Bitmap = new SKBitmap(width, height); + _canvas = new SKCanvas(Bitmap); + + var center = new SKPoint(width / 2f, height / 2f); + + int smallerDimension = Math.Min(width, height) - 8; + + // Get other diameters + float centerHoleDiameter = smallerDimension * parameters.CenterHole / parameters.DiscDiameter; + float clampingDiameter = smallerDimension * parameters.ClampingMinimum / parameters.DiscDiameter; + + float informationAreaStartDiameter = + smallerDimension * parameters.InformationAreaStart / parameters.DiscDiameter; + + float leadInEndDiameter = smallerDimension * parameters.LeadInEnd / parameters.DiscDiameter; + float informationAreaEndDiameter = smallerDimension * parameters.InformationAreaEnd / parameters.DiscDiameter; + + float recordableAreaStartDiameter = + smallerDimension * parameters.RecordableInformationStart / parameters.DiscDiameter; + + float recordableAreaEndDiameter = + smallerDimension * parameters.RecordableInformationEnd / parameters.DiscDiameter; + + _maxSector = parameters.NominalMaxSectors; + long lastSector1 = (long)lastSector; + + // If the dumped media is overburnt + if(lastSector1 > _maxSector) + _maxSector = lastSector1; + + // Ensure the disc hole is not painted over + var clipPath = new SKPath(); + clipPath.AddCircle(center.X, center.Y, centerHoleDiameter / 2); + _canvas.ClipPath(clipPath, SKClipOperation.Difference); + + // Paint CD + _canvas.DrawCircle(center, smallerDimension / 2f, new SKPaint + { + Style = SKPaintStyle.StrokeAndFill, + Color = parameters.DiscColor + }); + + // Draw out border of disc + _canvas.DrawCircle(center, smallerDimension / 2f, new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Black, + StrokeWidth = 4 + }); + + // Draw disc hole border + _canvas.DrawCircle(center, centerHoleDiameter / 2f, new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Black, + StrokeWidth = 4 + }); + + // Draw clamping area + _canvas.DrawCircle(center, clampingDiameter / 2f, new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Gray, + StrokeWidth = 4 + }); + + // Some trigonometry thing I do not understand fully + const float a = 1f; + + // Draw the Lead-In + _leadInPoints = GetSpiralPoints(center, informationAreaStartDiameter / 2, leadInEndDiameter / 2, a); + + var path = new SKPath(); + + path.MoveTo(_leadInPoints[0]); + + foreach(SKPoint point in _leadInPoints) + path.LineTo(point); + + _canvas.DrawPath(path, new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.LightGray, + StrokeWidth = 2 + }); + + // If there's a recordable information area, get its points + if(recordableAreaEndDiameter > 0 && + recordableAreaStartDiameter > 0) + _recordableInformationPoints = + GetSpiralPoints(center, recordableAreaStartDiameter / 2, recordableAreaEndDiameter / 2, a); + + _points = GetSpiralPoints(center, leadInEndDiameter / 2, informationAreaEndDiameter / 2, a); + + path = new SKPath(); + + path.MoveTo(_points[0]); + + long pointsPerSector = _points.Count / _maxSector; + long sectorsPerPoint = _maxSector / _points.Count; + + if(_maxSector % _points.Count > 0) + sectorsPerPoint++; + + long lastPoint; + + if(pointsPerSector > 0) + lastPoint = lastSector1 * pointsPerSector; + else + lastPoint = lastSector1 / sectorsPerPoint; + + for(int index = 0; index < lastPoint; index++) + { + SKPoint point = _points[index]; + path.LineTo(point); + } + + _canvas.DrawPath(path, new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Gray, + StrokeWidth = 2 + }); + } + + public SKBitmap Bitmap { get; } + + public static DiscParameters DiscParametersFromMediaType(MediaType mediaType, bool smallDisc = false) => + + // TODO: Blu-ray + // TODO: DDCD + // TODO: HD DVD + // TODO: UMD + // TODO: GD-ROM + mediaType switch + { + MediaType.CD => _cdParameters, + MediaType.CDDA => _cdParameters, + MediaType.CDG => _cdParameters, + MediaType.CDEG => _cdParameters, + MediaType.CDI => _cdParameters, + MediaType.CDIREADY => _cdParameters, + MediaType.CDROM => _cdParameters, + MediaType.CDROMXA => _cdParameters, + MediaType.CDPLUS => _cdParameters, + MediaType.CDMO => _cdParameters, + MediaType.VCD => _cdParameters, + MediaType.SVCD => _cdParameters, + MediaType.PCD => _cdParameters, + MediaType.DTSCD => _cdParameters, + MediaType.CDMIDI => _cdParameters, + MediaType.CDV => _cdParameters, + MediaType.CDR => _cdRecordableParameters, + MediaType.CDRW => _cdRewritableParameters, + MediaType.CDMRW => _cdRewritableParameters, + MediaType.SACD => _dvdParameters, + MediaType.DVDROM => smallDisc ? _dvdParameters : _dvdParameters80, + MediaType.DVDR => smallDisc ? _dvdRParameters : _dvdRParameters80, + MediaType.DVDRW => smallDisc ? _dvdRwParameters : _dvdRwParameters80, + MediaType.DVDPR => smallDisc ? _dvdPlusRParameters : _dvdPlusRParameters80, + MediaType.DVDPRW => smallDisc ? _dvdPlusRwParameters : _dvdPlusRwParameters80, + MediaType.DVDPRWDL => smallDisc ? _dvdPlusRwParameters : _dvdPlusRwParameters80, + MediaType.DVDRDL => smallDisc ? _dvdRParameters : _dvdRParameters80, + MediaType.DVDPRDL => smallDisc ? _dvdPlusRParameters : _dvdPlusRParameters80, + MediaType.DVDRWDL => smallDisc ? _dvdRwParameters : _dvdRwParameters80, + MediaType.PS1CD => _ps1CdParameters, + MediaType.PS2CD => _ps2CdParameters, + MediaType.PS2DVD => _dvdParameters, + MediaType.PS3DVD => _dvdParameters, + MediaType.XGD => _dvdParameters, + MediaType.XGD2 => _dvdParameters, + MediaType.XGD3 => _dvdParameters, + MediaType.XGD4 => _dvdParameters, + MediaType.MEGACD => _cdParameters, + MediaType.SATURNCD => _cdParameters, + MediaType.MilCD => _cdParameters, + MediaType.SuperCDROM2 => _cdParameters, + MediaType.JaguarCD => _cdParameters, + MediaType.ThreeDO => _cdParameters, + MediaType.PCFX => _cdParameters, + MediaType.NeoGeoCD => _cdParameters, + MediaType.CDTV => _cdParameters, + MediaType.CD32 => _cdParameters, + MediaType.Nuon => _dvdParameters, + MediaType.GOD => _dvdParameters80, + MediaType.WOD => _dvdParameters, + MediaType.Pippin => _cdParameters, + _ => null + }; + + /// Paints the segment of the spiral that corresponds to the specified sector in green + /// Sector + public void PaintSectorGood(ulong sector) => PaintSector(sector, SKColors.Green); + + /// Paints the segment of the spiral that corresponds to the specified sector in red + /// Sector + public void PaintSectorBad(ulong sector) => PaintSector(sector, SKColors.Red); + + /// Paints the segment of the spiral that corresponds to the specified sector in yellow + /// Sector + public void PaintSectorUnknown(ulong sector) => PaintSector(sector, SKColors.Yellow); + + /// Paints the segment of the spiral that corresponds to the specified sector in gray + /// Sector + public void PaintSectorUndumped(ulong sector) => PaintSector(sector, SKColors.Gray); + + /// Paints the segment of the spiral that corresponds to the information specific to recordable discs in green + public void PaintRecordableInformationGood() + { + if(_recordableInformationPoints is null) + return; + + var path = new SKPath(); + + path.MoveTo(_recordableInformationPoints[0]); + + foreach(SKPoint point in _recordableInformationPoints) + path.LineTo(point); + + _canvas.DrawPath(path, new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Green, + StrokeWidth = 2 + }); + } + + /// Paints the segment of the spiral that corresponds to the specified sector in the specified color + /// Sector + /// Color to paint the segment + public void PaintSector(ulong sector, SKColor color) + { + long pointsPerSector = _points.Count / _maxSector; + long sectorsPerPoint = _maxSector / _points.Count; + + if(_maxSector % _points.Count > 0) + sectorsPerPoint++; + + var paint = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = color, + StrokeWidth = 2 + }; + + var path = new SKPath(); + + if(pointsPerSector > 0) + { + long firstPoint = (long)sector * pointsPerSector; + + path.MoveTo(_points[(int)firstPoint]); + + for(int i = (int)firstPoint; i < firstPoint + pointsPerSector; i++) + path.LineTo(_points[i]); + + _canvas.DrawPath(path, paint); + + return; + } + + long point = (long)sector / sectorsPerPoint; + + if(point == 0) + { + path.MoveTo(_points[0]); + path.LineTo(_points[1]); + } + else if(point >= _points.Count - 1) + { + path.MoveTo(_points[^2]); + path.LineTo(_points[^1]); + } + else + { + path.MoveTo(_points[(int)point]); + path.LineTo(_points[(int)point + 1]); + } + + _canvas.DrawPath(path, paint); + } + + /// + /// Paints the segment of the spiral that corresponds to the specified sector of the Lead-In in the specified + /// color + /// + /// Sector + /// Color to paint the segment in + /// Total size of the lead-in in sectors + public void PaintLeadInSector(ulong sector, SKColor color, int leadInSize) + { + long pointsPerSector = _leadInPoints.Count / leadInSize; + long sectorsPerPoint = leadInSize / _leadInPoints.Count; + + if(leadInSize % _leadInPoints.Count > 0) + sectorsPerPoint++; + + var paint = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = color, + StrokeWidth = 2 + }; + + var path = new SKPath(); + + if(pointsPerSector > 0) + { + long firstPoint = (long)sector * pointsPerSector; + + path.MoveTo(_leadInPoints[(int)firstPoint]); + + for(int i = (int)firstPoint; i < firstPoint + pointsPerSector; i++) + path.LineTo(_leadInPoints[i]); + + _canvas.DrawPath(path, paint); + + return; + } + + long point = (long)sector / sectorsPerPoint; + + if(point == 0) + { + path.MoveTo(_leadInPoints[0]); + path.LineTo(_leadInPoints[1]); + } + else if(point >= _leadInPoints.Count - 1) + { + path.MoveTo(_leadInPoints[^2]); + path.LineTo(_leadInPoints[^1]); + } + else + { + path.MoveTo(_leadInPoints[(int)point]); + path.LineTo(_leadInPoints[(int)point + 1]); + } + + _canvas.DrawPath(path, paint); + } + + /// Writes the spiral bitmap as a PNG into the specified stream + /// Stream that will receive the spiral bitmap + public void WriteToStream(Stream stream) + { + var image = SKImage.FromBitmap(Bitmap); + SKData data = image.Encode(); + data.SaveTo(stream); + } + + /// Gets all the points that are needed to draw a spiral with the specified parameters + /// Center of the spiral start + /// Minimum radius before which the spiral must have no points + /// Radius at which the spiral will end + /// TODO: Something trigonometry something something... + /// List of points to draw the specified spiral + static List GetSpiralPoints(SKPoint center, float minRadius, float maxRadius, float A) + { + // Get the points. + List points = new(); + const float dtheta = (float)(0.5 * Math.PI / 180); // Five degrees. + + for(float theta = 0;; theta += dtheta) + { + // Calculate r. + float r = A * theta; + + if(r < minRadius) + continue; + + // Convert to Cartesian coordinates. + float x = (float)(r * Math.Cos(theta)); + float y = (float)(r * Math.Sin(theta)); + + // Center. + x += center.X; + y += center.Y; + + // Create the point. + points.Add(new SKPoint(x, y)); + + // If we have gone far enough, stop. + if(r > maxRadius) + break; + } + + return points; + } + + // GD-ROM LD area ends at 29mm, HD area starts at 30mm radius + + /// Defines the physical disc parameters + /// Diameter of the whole disc + /// Diameter of the hole at the center + /// Diameter of the clamping area + /// Diameter at which the information area starts + /// Diameter at which the Lead-In starts + /// Diameter at which the information area ends + /// Diameter at which the information specific to recordable media starts + /// Diameter at which the information specific to recordable media starts + /// Number of maximum sectors, for discs following the specifications + /// Typical disc color + public sealed record DiscParameters(float DiscDiameter, float CenterHole, float ClampingMinimum, + float InformationAreaStart, float LeadInEnd, float InformationAreaEnd, + float RecordableInformationStart, float RecordableInformationEnd, + int NominalMaxSectors, SKColor DiscColor); +} \ No newline at end of file