mirror of
https://github.com/cyanfish/naps2.git
synced 2024-09-11 15:26:55 +03:00
Parse image dimensions from JPEG headers when directly embedding JPEGs in PDFs
This avoids the need to load the image at all and is much faster and more memory efficient for a common case.
This commit is contained in:
parent
c98ae6a470
commit
6b3b8ea030
@ -4,25 +4,59 @@ internal static class JpegFormatHelper
|
|||||||
{
|
{
|
||||||
// JPEG format doc: https://github.com/corkami/formats/blob/master/image/jpeg.md
|
// JPEG format doc: https://github.com/corkami/formats/blob/master/image/jpeg.md
|
||||||
|
|
||||||
public static int ReadNumComponents(FileStream stream)
|
public static JpegHeader? ReadHeader(FileStream stream)
|
||||||
{
|
{
|
||||||
var sig1 = stream.ReadByte();
|
var sig1 = stream.ReadByte();
|
||||||
var sig2 = stream.ReadByte();
|
var sig2 = stream.ReadByte();
|
||||||
if (sig1 != 0xFF || sig2 != 0xD8) return -1;
|
if (sig1 != 0xFF || sig2 != 0xD8) return null;
|
||||||
|
var header = new JpegHeader(0, 0, 0, 0, 0);
|
||||||
|
bool isJfif = false;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var marker = stream.ReadByte();
|
var marker = stream.ReadByte();
|
||||||
if (marker != 0xFF) return -1;
|
if (marker != 0xFF) return null;
|
||||||
var type = stream.ReadByte();
|
var type = stream.ReadByte();
|
||||||
if (type == -1) return -1;
|
if (type == -1) return null;
|
||||||
var len = stream.ReadByte() * 256 + stream.ReadByte() - 2;
|
var len = stream.ReadByte() * 256 + stream.ReadByte() - 2;
|
||||||
if (len <= 0) return -1;
|
if (len <= 0) return null;
|
||||||
var buf = new byte[len];
|
var buf = new byte[len];
|
||||||
if (stream.Read(buf, 0, len) < len) return -1;
|
if (stream.Read(buf, 0, len) < len) return null;
|
||||||
|
if (type == 0xE0 && len >= 12 && buf is [0x4A, 0x46, 0x49, 0x46, 0x00, ..]) // Application data (JFIF)
|
||||||
|
{
|
||||||
|
isJfif = true;
|
||||||
|
var units = buf[7];
|
||||||
|
if (units == 1) // Pixels per inch
|
||||||
|
{
|
||||||
|
header = header with
|
||||||
|
{
|
||||||
|
HorizontalDpi = buf[8] * 256 + buf[9],
|
||||||
|
VerticalDpi = buf[10] * 256 + buf[11]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (units == 2) // Pixels per cm
|
||||||
|
{
|
||||||
|
header = header with
|
||||||
|
{
|
||||||
|
HorizontalDpi = (buf[8] * 256 + buf[9]) * 2.54,
|
||||||
|
VerticalDpi = (buf[10] * 256 + buf[11]) * 2.54
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
if (type == 0xC0 && len >= 6) // Start of frame
|
if (type == 0xC0 && len >= 6) // Start of frame
|
||||||
{
|
{
|
||||||
return buf[5];
|
// JPEGs can come with several header varieties: JFIF-only, EXIF-only, or JFIF+EXIF.
|
||||||
|
// As long as we have a JFIF header we should have accurate resolution information.
|
||||||
|
// If it's EXIF-only we're not currently able to parse that.
|
||||||
|
if (!isJfif) return null;
|
||||||
|
return header with
|
||||||
|
{
|
||||||
|
Height = buf[1] * 256 + buf[2],
|
||||||
|
Width = buf[3] * 256 + buf[4],
|
||||||
|
NumComponents = buf[5]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record JpegHeader(double HorizontalDpi, double VerticalDpi, int Width, int Height, int NumComponents);
|
||||||
}
|
}
|
@ -129,7 +129,7 @@ public class PdfExporter : IPdfExporter
|
|||||||
// We can't use a DisposableList as the objects we need to dispose are generated on the fly
|
// We can't use a DisposableList as the objects we need to dispose are generated on the fly
|
||||||
foreach (var state in imagePages.Concat(pdfPages))
|
foreach (var state in imagePages.Concat(pdfPages))
|
||||||
{
|
{
|
||||||
state.RenderedImage?.Dispose();
|
state.Embedder?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -244,18 +244,34 @@ public class PdfExporter : IPdfExporter
|
|||||||
private PageExportState RenderStep(PageExportState state)
|
private PageExportState RenderStep(PageExportState state)
|
||||||
{
|
{
|
||||||
if (state.CancelToken.IsCancellationRequested) return state;
|
if (state.CancelToken.IsCancellationRequested) return state;
|
||||||
state.RenderedImage = state.Image.Render();
|
state.Embedder = GetRenderedImageOrDirectJpegEmbedder(state);
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IEmbedder GetRenderedImageOrDirectJpegEmbedder(PageExportState state)
|
||||||
|
{
|
||||||
|
if (state.Image is { Storage: ImageFileStorage fileStorage, TransformState.IsEmpty: true } &&
|
||||||
|
ImageContext.GetFileFormatFromExtension(fileStorage.FullPath) == ImageFileFormat.Jpeg)
|
||||||
|
{
|
||||||
|
// Special case if we have an un-transformed JPEG - just use the original file instead of re-encoding
|
||||||
|
using var fileStream = new FileStream(fileStorage.FullPath, FileMode.Open, FileAccess.Read);
|
||||||
|
var jpegHeader = JpegFormatHelper.ReadHeader(fileStream);
|
||||||
|
// Ensure it's not a grayscale image as those are known to not be embeddable
|
||||||
|
if (jpegHeader is { NumComponents: > 1 })
|
||||||
|
{
|
||||||
|
return new DirectJpegEmbedder(jpegHeader, fileStorage.FullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new RenderedImageEmbedder(state.Image.Render());
|
||||||
|
}
|
||||||
|
|
||||||
private PageExportState WriteToPdfSharpStep(PageExportState state)
|
private PageExportState WriteToPdfSharpStep(PageExportState state)
|
||||||
{
|
{
|
||||||
if (state.CancelToken.IsCancellationRequested) return state;
|
if (state.CancelToken.IsCancellationRequested) return state;
|
||||||
lock (state.Document)
|
lock (state.Document)
|
||||||
{
|
{
|
||||||
var exportFormat = PrepareForExport(state);
|
var exportFormat = state.Embedder!.PrepareForExport(state.Image.Metadata);
|
||||||
DrawImageOnPage(state.Page, state.Image, state.RenderedImage!, state.Image.Metadata.PageSize, exportFormat,
|
DrawImageOnPage(state.Page, state.Embedder, state.Image.Metadata.PageSize, exportFormat, state.Compat);
|
||||||
state.Compat);
|
|
||||||
if (state.OcrTask?.Result != null)
|
if (state.OcrTask?.Result != null)
|
||||||
{
|
{
|
||||||
DrawOcrTextOnPage(state.Page, state.OcrTask.Result);
|
DrawOcrTextOnPage(state.Page, state.OcrTask.Result);
|
||||||
@ -265,22 +281,6 @@ public class PdfExporter : IPdfExporter
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImageExportFormat PrepareForExport(PageExportState state)
|
|
||||||
{
|
|
||||||
var exportFormat = new ImageExportHelper()
|
|
||||||
.GetExportFormat(state.RenderedImage!, state.Image.Metadata.BitDepth, state.Image.Metadata.Lossless);
|
|
||||||
if (exportFormat.FileFormat == ImageFileFormat.Unspecified)
|
|
||||||
{
|
|
||||||
exportFormat = exportFormat with { FileFormat = ImageFileFormat.Jpeg };
|
|
||||||
}
|
|
||||||
if (exportFormat.PixelFormat == ImagePixelFormat.BW1 &&
|
|
||||||
state.RenderedImage!.LogicalPixelFormat != ImagePixelFormat.BW1)
|
|
||||||
{
|
|
||||||
state.RenderedImage = state.RenderedImage.PerformTransform(new BlackWhiteTransform());
|
|
||||||
}
|
|
||||||
return exportFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MemoryStream FinalizeAndSaveDocument(PdfDocument document, PdfExportParams exportParams)
|
private static MemoryStream FinalizeAndSaveDocument(PdfDocument document, PdfExportParams exportParams)
|
||||||
{
|
{
|
||||||
var compat = exportParams.Compat;
|
var compat = exportParams.Compat;
|
||||||
@ -325,14 +325,15 @@ public class PdfExporter : IPdfExporter
|
|||||||
private PageExportState InitOcrStep(PageExportState state)
|
private PageExportState InitOcrStep(PageExportState state)
|
||||||
{
|
{
|
||||||
if (state.CancelToken.IsCancellationRequested) return state;
|
if (state.CancelToken.IsCancellationRequested) return state;
|
||||||
var ext = state.RenderedImage!.OriginalFileFormat == ImageFileFormat.Png ? ".png" : ".jpg";
|
var ext = state.Embedder!.OriginalFileFormat == ImageFileFormat.Png ? ".png" : ".jpg";
|
||||||
string ocrTempFilePath = Path.Combine(_scanningContext.TempFolderPath, Path.GetRandomFileName() + ext);
|
string ocrTempFilePath = Path.Combine(_scanningContext.TempFolderPath, Path.GetRandomFileName() + ext);
|
||||||
if (!_scanningContext.OcrRequestQueue.HasCachedResult(state.OcrEngine!, state.Image, state.OcrParams!))
|
if (!_scanningContext.OcrRequestQueue.HasCachedResult(state.OcrEngine!, state.Image, state.OcrParams!))
|
||||||
{
|
{
|
||||||
// Save the image to a file for use in OCR.
|
// Save the image to a file for use in OCR.
|
||||||
// We don't need to delete this file as long as we pass it to OcrRequestQueue.Enqueue, which takes
|
// We don't need to delete this file as long as we pass it to OcrRequestQueue.Enqueue, which takes
|
||||||
// ownership and guarantees its eventual deletion.
|
// ownership and guarantees its eventual deletion.
|
||||||
state.RenderedImage!.Save(ocrTempFilePath);
|
using var fileStream = new FileStream(ocrTempFilePath, FileMode.Create, FileAccess.Write);
|
||||||
|
state.Embedder.CopyToStream(fileStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start OCR
|
// Start OCR
|
||||||
@ -457,42 +458,42 @@ public class PdfExporter : IPdfExporter
|
|||||||
return string.Concat(elements);
|
return string.Concat(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawImageOnPage(PdfPage page, ProcessedImage source, IMemoryImage image, PageSize? pageSize,
|
private void DrawImageOnPage(PdfPage page, IEmbedder embedder, PageSize? pageSize, ImageExportFormat exportFormat,
|
||||||
ImageExportFormat exportFormat, PdfCompat compat)
|
PdfCompat compat)
|
||||||
{
|
{
|
||||||
using var xImage = XImage.FromImageSource(new ImageSource(source, image, exportFormat));
|
using var xImage = XImage.FromImageSource(new ImageSource(embedder, exportFormat));
|
||||||
if (compat != PdfCompat.Default)
|
if (compat != PdfCompat.Default)
|
||||||
{
|
{
|
||||||
xImage.Interpolate = false;
|
xImage.Interpolate = false;
|
||||||
}
|
}
|
||||||
var (realWidth, realHeight) = GetRealSize(image, pageSize);
|
var (realWidth, realHeight) = GetRealSize(embedder, pageSize);
|
||||||
page.Width = realWidth;
|
page.Width = realWidth;
|
||||||
page.Height = realHeight;
|
page.Height = realHeight;
|
||||||
using XGraphics gfx = XGraphics.FromPdfPage(page);
|
using XGraphics gfx = XGraphics.FromPdfPage(page);
|
||||||
gfx.DrawImage(xImage, 0, 0, realWidth, realHeight);
|
gfx.DrawImage(xImage, 0, 0, realWidth, realHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (double width, double height) GetRealSize(IMemoryImage img, PageSize? pageSize)
|
private static (double width, double height) GetRealSize(IEmbedder embedder, PageSize? pageSize)
|
||||||
{
|
{
|
||||||
double hAdjust = 72 / img.HorizontalResolution;
|
double hAdjust = 72 / embedder.HorizontalDpi;
|
||||||
double vAdjust = 72 / img.VerticalResolution;
|
double vAdjust = 72 / embedder.VerticalDpi;
|
||||||
if (double.IsInfinity(hAdjust) || double.IsInfinity(vAdjust))
|
if (double.IsInfinity(hAdjust) || double.IsInfinity(vAdjust))
|
||||||
{
|
{
|
||||||
hAdjust = vAdjust = 0.75;
|
hAdjust = vAdjust = 0.75;
|
||||||
}
|
}
|
||||||
double realWidth = img.Width * hAdjust;
|
double realWidth = embedder.Width * hAdjust;
|
||||||
double realHeight = img.Height * vAdjust;
|
double realHeight = embedder.Height * vAdjust;
|
||||||
|
|
||||||
// Use the scanned page size if it's close enough
|
// Use the scanned page size if it's close enough
|
||||||
// It might not be close enough if we've cropped the image or if the scanner didn't produce the requested size
|
// It might not be close enough if we've cropped the image or if the scanner didn't produce the requested size
|
||||||
if (pageSize != null)
|
if (pageSize != null)
|
||||||
{
|
{
|
||||||
var pageHorDpi = img.Width / (double) pageSize.WidthInInches;
|
var pageHorDpi = embedder.Width / (double) pageSize.WidthInInches;
|
||||||
var pageVerDpi = img.Height / (double) pageSize.HeightInInches;
|
var pageVerDpi = embedder.Height / (double) pageSize.HeightInInches;
|
||||||
// We expect a margin of error of <1 since most of the inaccuracy comes from file formats like JPEG only
|
// We expect a margin of error of <1 since most of the inaccuracy comes from file formats like JPEG only
|
||||||
// storing integral DPIs
|
// storing integral DPIs
|
||||||
if (Math.Abs(img.HorizontalResolution - pageHorDpi) <= 1 &&
|
if (Math.Abs(embedder.HorizontalDpi - pageHorDpi) <= 1 &&
|
||||||
Math.Abs(img.VerticalResolution - pageVerDpi) <= 1)
|
Math.Abs(embedder.VerticalDpi - pageVerDpi) <= 1)
|
||||||
{
|
{
|
||||||
realWidth = (double) pageSize.WidthInInches * 72;
|
realWidth = (double) pageSize.WidthInInches * 72;
|
||||||
realHeight = (double) pageSize.HeightInInches * 72;
|
realHeight = (double) pageSize.HeightInInches * 72;
|
||||||
@ -581,45 +582,30 @@ public class PdfExporter : IPdfExporter
|
|||||||
public PdfCompat Compat { get; }
|
public PdfCompat Compat { get; }
|
||||||
|
|
||||||
public bool NeedsOcr { get; set; }
|
public bool NeedsOcr { get; set; }
|
||||||
public IMemoryImage? RenderedImage { get; set; }
|
public IEmbedder? Embedder { get; set; }
|
||||||
public Task<OcrResult?>? OcrTask { get; set; }
|
public Task<OcrResult?>? OcrTask { get; set; }
|
||||||
public PdfDocument? PageDocument { get; set; }
|
public PdfDocument? PageDocument { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ImageSource : IImageSource
|
private class ImageSource : IImageSource
|
||||||
{
|
{
|
||||||
private readonly ProcessedImage _source;
|
private readonly IEmbedder _embedder;
|
||||||
private readonly IMemoryImage _image;
|
|
||||||
private readonly ImageExportFormat _exportFormat;
|
private readonly ImageExportFormat _exportFormat;
|
||||||
|
|
||||||
public ImageSource(ProcessedImage source, IMemoryImage image, ImageExportFormat exportFormat)
|
public ImageSource(IEmbedder embedder, ImageExportFormat exportFormat)
|
||||||
{
|
{
|
||||||
_source = source;
|
_embedder = embedder;
|
||||||
_image = image;
|
|
||||||
_exportFormat = exportFormat;
|
_exportFormat = exportFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveAsJpeg(MemoryStream ms)
|
public void SaveAsJpeg(MemoryStream ms)
|
||||||
{
|
{
|
||||||
if (_source is { Storage: ImageFileStorage fileStorage, TransformState.IsEmpty: true } &&
|
_embedder.CopyToStream(ms);
|
||||||
ImageContext.GetFileFormatFromExtension(fileStorage.FullPath) == ImageFileFormat.Jpeg)
|
|
||||||
{
|
|
||||||
// Special case if we have an un-transformed JPEG - just use the original file instead of re-encoding
|
|
||||||
using var fileStream = new FileStream(fileStorage.FullPath, FileMode.Open, FileAccess.Read);
|
|
||||||
// Ensure it's not a grayscale image as those are known to not be embeddable
|
|
||||||
if (JpegFormatHelper.ReadNumComponents(fileStream) > 1)
|
|
||||||
{
|
|
||||||
fileStream.Seek(0, SeekOrigin.Begin);
|
|
||||||
fileStream.CopyTo(ms);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// PDFs require RGB channels so we need to make sure we're exporting that.
|
|
||||||
_image.Save(ms, ImageFileFormat.Jpeg, new ImageSaveOptions { PixelFormatHint = ImagePixelFormat.RGB24 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveAsPdfBitmap(MemoryStream ms)
|
public void SaveAsPdfBitmap(MemoryStream ms)
|
||||||
{
|
{
|
||||||
|
var image = _embedder.Image;
|
||||||
var subPixelType = _exportFormat.PixelFormat switch
|
var subPixelType = _exportFormat.PixelFormat switch
|
||||||
{
|
{
|
||||||
ImagePixelFormat.ARGB32 => SubPixelType.Bgra,
|
ImagePixelFormat.ARGB32 => SubPixelType.Bgra,
|
||||||
@ -627,23 +613,24 @@ public class PdfExporter : IPdfExporter
|
|||||||
_ => throw new InvalidOperationException("Expected 8/24/32 bit bitmap")
|
_ => throw new InvalidOperationException("Expected 8/24/32 bit bitmap")
|
||||||
};
|
};
|
||||||
var dstPixelInfo =
|
var dstPixelInfo =
|
||||||
new PixelInfo(_image.Width, _image.Height, subPixelType, strideAlign: 4) { InvertY = true };
|
new PixelInfo(image.Width, image.Height, subPixelType, strideAlign: 4) { InvertY = true };
|
||||||
ms.SetLength(dstPixelInfo.Length);
|
ms.SetLength(dstPixelInfo.Length);
|
||||||
new CopyBitwiseImageOp().Perform(_image, ms.GetBuffer(), dstPixelInfo);
|
new CopyBitwiseImageOp().Perform(image, ms.GetBuffer(), dstPixelInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveAsPdfIndexedBitmap(MemoryStream ms)
|
public void SaveAsPdfIndexedBitmap(MemoryStream ms)
|
||||||
{
|
{
|
||||||
if (_image.LogicalPixelFormat != ImagePixelFormat.BW1)
|
var image = _embedder.Image;
|
||||||
|
if (image.LogicalPixelFormat != ImagePixelFormat.BW1)
|
||||||
throw new InvalidOperationException("Expected 1 bit bitmap");
|
throw new InvalidOperationException("Expected 1 bit bitmap");
|
||||||
var dstPixelInfo =
|
var dstPixelInfo =
|
||||||
new PixelInfo(_image.Width, _image.Height, SubPixelType.Bit) { InvertY = true };
|
new PixelInfo(image.Width, image.Height, SubPixelType.Bit) { InvertY = true };
|
||||||
ms.SetLength(dstPixelInfo.Length);
|
ms.SetLength(dstPixelInfo.Length);
|
||||||
new CopyBitwiseImageOp().Perform(_image, ms.GetBuffer(), dstPixelInfo);
|
new CopyBitwiseImageOp().Perform(image, ms.GetBuffer(), dstPixelInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Width => _image.Width;
|
public int Width => _embedder.Width;
|
||||||
public int Height => _image.Height;
|
public int Height => _embedder.Height;
|
||||||
public string? Name => null;
|
public string? Name => null;
|
||||||
|
|
||||||
public XImageFormat ImageFormat
|
public XImageFormat ImageFormat
|
||||||
@ -671,4 +658,91 @@ public class PdfExporter : IPdfExporter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private interface IEmbedder : IDisposable
|
||||||
|
{
|
||||||
|
void CopyToStream(Stream stream);
|
||||||
|
ImageExportFormat PrepareForExport(ImageMetadata metadata);
|
||||||
|
IMemoryImage Image { get; }
|
||||||
|
int Width { get; }
|
||||||
|
int Height { get; }
|
||||||
|
double HorizontalDpi { get; }
|
||||||
|
double VerticalDpi { get; }
|
||||||
|
ImageFileFormat OriginalFileFormat { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RenderedImageEmbedder : IEmbedder
|
||||||
|
{
|
||||||
|
public RenderedImageEmbedder(IMemoryImage image)
|
||||||
|
{
|
||||||
|
Image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMemoryImage Image { get; private set; }
|
||||||
|
public int Width => Image.Width;
|
||||||
|
public int Height => Image.Height;
|
||||||
|
public double HorizontalDpi => Image.HorizontalResolution;
|
||||||
|
public double VerticalDpi => Image.VerticalResolution;
|
||||||
|
public ImageFileFormat OriginalFileFormat => Image.OriginalFileFormat;
|
||||||
|
|
||||||
|
public void CopyToStream(Stream stream)
|
||||||
|
{
|
||||||
|
// PDFs require RGB channels so we need to make sure we're exporting that.
|
||||||
|
Image.Save(stream, ImageFileFormat.Jpeg, new ImageSaveOptions { PixelFormatHint = ImagePixelFormat.RGB24 });
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageExportFormat PrepareForExport(ImageMetadata metadata)
|
||||||
|
{
|
||||||
|
var exportFormat = new ImageExportHelper().GetExportFormat(Image, metadata.BitDepth, metadata.Lossless);
|
||||||
|
if (exportFormat.FileFormat == ImageFileFormat.Unspecified)
|
||||||
|
{
|
||||||
|
exportFormat = exportFormat with { FileFormat = ImageFileFormat.Jpeg };
|
||||||
|
}
|
||||||
|
if (exportFormat.PixelFormat == ImagePixelFormat.BW1 &&
|
||||||
|
Image.LogicalPixelFormat != ImagePixelFormat.BW1)
|
||||||
|
{
|
||||||
|
Image = Image.PerformTransform(new BlackWhiteTransform());
|
||||||
|
}
|
||||||
|
return exportFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Image.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DirectJpegEmbedder : IEmbedder
|
||||||
|
{
|
||||||
|
private readonly JpegFormatHelper.JpegHeader _header;
|
||||||
|
private readonly string _path;
|
||||||
|
|
||||||
|
public DirectJpegEmbedder(JpegFormatHelper.JpegHeader header, string path)
|
||||||
|
{
|
||||||
|
_header = header;
|
||||||
|
_path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMemoryImage Image => throw new InvalidOperationException();
|
||||||
|
public int Width => _header.Width;
|
||||||
|
public int Height => _header.Height;
|
||||||
|
public double HorizontalDpi => _header.HorizontalDpi;
|
||||||
|
public double VerticalDpi => _header.VerticalDpi;
|
||||||
|
public ImageFileFormat OriginalFileFormat => ImageFileFormat.Jpeg;
|
||||||
|
|
||||||
|
public void CopyToStream(Stream stream)
|
||||||
|
{
|
||||||
|
using var fileStream = new FileStream(_path, FileMode.Open, FileAccess.Read);
|
||||||
|
fileStream.CopyTo(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageExportFormat PrepareForExport(ImageMetadata metadata)
|
||||||
|
{
|
||||||
|
return new ImageExportFormat(ImageFileFormat.Jpeg, ImagePixelFormat.RGB24);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -15,6 +15,7 @@
|
|||||||
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue"><data><IncludeFilters /><ExcludeFilters /></data></s:String>
|
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue"><data><IncludeFilters /><ExcludeFilters /></data></s:String>
|
||||||
<s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue"><data /></s:String>
|
<s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue"><data /></s:String>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=creds/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=creds/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=embedder/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Escl/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Escl/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Grpc/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Grpc/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=hwnd/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=hwnd/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
Loading…
Reference in New Issue
Block a user