Обновление PDF-изображения на месте - PullRequest
1 голос
/ 18 февраля 2020

Я пытаюсь заменить поток изображения в документе SDF, используя PDFNet 7.0.4 и netcoreapp3.1. В максимально возможной степени я хочу сохранить исходный объект и его метаданные; те же размеры, цветовая система, компрессия и т. д. c. В идеале номер объекта и даже генерация должны оставаться неизменными - цель состоит в том, чтобы сравнение до и после показывало только измененные пиксели в потоке.

Я получаю необработанные данные пикселей в виде объекта Stream с помощью этого метода:

private Stream GetImageData(int objectNum)
{
    var image = new PDF.Image(sdfDoc.GetObj(objectNum));

    var bits = image.GetBitsPerComponent();
    var channels = image.GetComponentNum();
    var bytesPerChannel = bits / 8;
    var height = image.GetImageHeight();
    var width = image.GetImageWidth();

    var data = image.GetImageData();
    var len = height * width * channels * bytesPerChannel;

    using (var reader = new pdftron.Filters.FilterReader(data))
    {
        var buffer = new byte[len];
        reader.Read(buffer);                

        return new MemoryStream(buffer);
    }
}

После манипулирования данными изображения я хочу обновить его перед сохранением базового SDFDoc объект. Я попытался использовать следующий метод:

private void SetImageData(int objectNum, Stream stream)
{
    var image = new PDF.Image(sdfDoc.GetObj(objectNum));

    var bits = image.GetBitsPerComponent();
    var channels = image.GetComponentNum();
    var bytesPerChannel = bits / 8;
    var height = image.GetImageHeight();
    var width = image.GetImageWidth();

    var len = height * width * channels * bytesPerChannel;
    if (stream.Length != len) { throw new DataMisalignedException("Stream length does not match expected image dimensions"); }

    using (var ms = new MemoryStream())
    using (var writer = new pdftron.Filters.FilterWriter(image.GetImageData()))
    {
        stream.CopyTo(ms);
        writer.WriteBuffer(ms.ToArray());
    }
}

Это работает без ошибок, но на самом деле ничего не обновляется. Я пытался поиграть с SDFObj.SetStreamData(), но так и не смог сделать это. Каков наименьший эффект и самый быстрый способ напрямую заменить только необработанные пиксельные данные в потоке изображения?


edit

У меня есть эта половина работы с этот метод:

private void SetImageData(int objectNum, Stream stream)
{
    var sdfObj = sdfDoc.GetObj(objectNum);
    var image = new PDF.Image(sdfObj);

    var bits = image.GetBitsPerComponent();
    var channels = image.GetComponentNum();
    var bytesPerChannel = bits / 8;
    var height = image.GetImageHeight();
    var width = image.GetImageWidth();

    var len = height * width * channels * bytesPerChannel;
    if (stream.Length != len) { throw new DataMisalignedException("Stream length does not match expected image dimensions"); }

    var buffer = new byte[len];
    stream.Read(buffer, 0, len);
    sdfObj.SetStreamData(buffer);
    sdfObj.Erase("Filters");
}

Это работает, как и ожидалось, но с очевидным предостережением, что он просто игнорирует любое существующее сжатие и превращает изображение в необработанный несжатый поток.

Я пробовал sdfObj.SetStreamData(buffer, image.GetImageData()); и sdfObj.SetStreamData(buffer, image.GetImageData().GetAttachedFilter());, и это обновляет объект в файле, но результирующее изображение не отображается.

1 Ответ

1 голос
/ 22 февраля 2020

Следующий код показывает, как сохранить объект Image, но изменить фактические данные потока.

static private Stream GetImageData(Obj o)
{
    var image = new pdftron.PDF.Image(o);

    var bits = image.GetBitsPerComponent();
    var channels = image.GetComponentNum();
    var bytesPerChannel = bits / 8;
    var height = image.GetImageHeight();
    var width = image.GetImageWidth();

    var data = image.GetImageData();
    var len = height * width * channels * bytesPerChannel;

    using (var reader = new pdftron.Filters.FilterReader(data))
    {
        var buffer = new byte[len];
        reader.Read(buffer);
        return new MemoryStream(buffer);
    }
}

static private void SetImageData(PDFDoc doc, Obj o, Stream stream)
{

    var image = new pdftron.PDF.Image(o);

    var bits = image.GetBitsPerComponent();
    var channels = image.GetComponentNum();
    var bytesPerChannel = bits / 8;
    var height = image.GetImageHeight();
    var width = image.GetImageWidth();

    var len = height * width * channels * bytesPerChannel;
    if (stream.Length != len) { throw new DataMisalignedException("Stream length does not match expected image dimensions"); }

    o.Erase("DecodeParms"); // Important: this won'be accurate after SetStreamData
    // now we actually do the stream swap
    o.SetStreamData((stream as MemoryStream).ToArray(), new FlateEncode(null));
}

static private void InvertPixels(Stream stream)
{
    // This function is for DEMO purposes
    // this code assumes 3 channel 8bit
    long length = stream.Length;
    long pixels = length / 3;
    for(int p = 0; p < pixels; ++p)
    {
        int c1 = stream.ReadByte();
        int c2 = stream.ReadByte();
        int c3 = stream.ReadByte();
        stream.Seek(-3, SeekOrigin.Current);
        stream.WriteByte((byte)(255 - c1));
        stream.WriteByte((byte)(255 - c2));
        stream.WriteByte((byte)(255 - c3));
    }
    stream.Seek(0, SeekOrigin.Begin);
}

И вот пример кода для использования.

static void Main(string[] args)
{
    PDFNet.Initialize();

    var x = new PDFDoc(@"2002.04610.pdf");
    x.InitSecurityHandler();

    var o = x.GetSDFDoc().GetObj(381);
    Stream source = GetImageData(o);
    InvertPixels(source);
    SetImageData(x, o, source);
    x.Save(@"2002.04610-MOD.pdf", SDFDoc.SaveOptions.e_remove_unused);
}
...