본문 바로가기
C++

[C++/winrt] Windows::Data::Pdf 이용하여 PDF 파일을 Floyd-Steinberg Dithering 처리 후 LibTiff를 이용하여 MultiTiFF로 저장 예제 입니다.

by Hwoarang757 2024. 11. 20.

품질 개선 방안을 계속 모색 해보고 있습니다...!

 

Console 프로그램으로 실행 예시는 아래와 같습니다.

#> WindowDataPdf.exe "1" "C:\Users\user\Downloads\test.pdf" "C:\Users\user\Downloads"

 

#include "pch.h"
#include <iostream>
#include <vector>

#include <tiff.h>
#include <tiffio.h>

#include <wrl/client.h>


namespace winrt 
{
    using namespace Windows::Foundation;
    using namespace Windows::Storage;
    using namespace Windows::Data::Pdf;
    using namespace Windows::Graphics::Imaging;
}
winrt::Windows::Graphics::Imaging::SoftwareBitmap FloydSteinbergDithering(winrt::Windows::Graphics::Imaging::SoftwareBitmap const& inputBitmap);
winrt::Windows::Graphics::Imaging::SoftwareBitmap ConvertGrayScale(winrt::Windows::Graphics::Imaging::SoftwareBitmap const& bitmap);
std::vector<uint8_t> ConvertToWhiteAndBlack(winrt::Windows::Graphics::Imaging::SoftwareBitmap const& bitmap, uint32_t& width, uint32_t& height, uint8_t threshold = 128);

struct __declspec(uuid("5b0d3235-4dba-4d44-865e-8f1d0e4fd04d")) IMemoryBufferByteAccess : public IUnknown {
    virtual HRESULT __stdcall GetBuffer(BYTE** value, UINT32* capacity) = 0;
};



/*
*
* [0]. id
* [1]. source file fullpath
* [2]. bmp files save path
*
* return value
* 결과|PageCount|DESC
*
*/

int wmain(int argc, wchar_t* argv[])
{
    if (argc != 4) { // 첫번째 매개변수는 실행되는 프로세스 명
        std::wcout << L"FAILED|0|매개변수가 3개가 전달 되지 않았습니다." << std::endl;
        return -1;
    }

    winrt::hstring Id;
    winrt::hstring sourcePath;
    winrt::hstring savePath;

    //std::wstring tempStr;
    //std::wstring replaceStr;
    for (int i = 0; i < argc; i++) {
        //tempStr = argv[i];
        //replaceStr = std::regex_replace(tempStr, std::wregex(L"\\\\"), L"/");

        if (i == 1) Id = winrt::hstring(argv[i]);
        if (i == 2) sourcePath = winrt::hstring(argv[i]);
        if (i == 3) savePath = winrt::hstring(argv[i]);
    }


    //std::wcout << Id.c_str() << "," << sourcePath.c_str() << "," << savePath.c_str() << std::endl;
    winrt::init_apartment();
    //Uri uri(L"http://aka.ms/cppwinrt");
    //printf("Hello, %ls!\n", uri.AbsoluteUri().c_str());

    //try {
        winrt::Windows::Storage::StorageFile storagePdfFile = winrt::Windows::Storage::StorageFile::GetFileFromPathAsync(sourcePath).get();
        winrt::Windows::Data::Pdf::PdfDocument pdfDocument = winrt::Windows::Data::Pdf::PdfDocument::LoadFromFileAsync(storagePdfFile, L"").get();

        int nCount = pdfDocument.PageCount();
        int nPageIndex = 0;

        /*
        try {
            StorageFolder storageFolder = ApplicationData::Current().LocalFolder();
        }
        catch (hresult_error const& e) {
            wprintf_s(L"error 0x%08X - %ls\n", e.code() , e.message().c_str());
        }
        */

        winrt::Windows::Storage::StorageFolder storageFolder = winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(savePath).get();

        TIFF* tiff = TIFFOpenW(L"C:\\Users\\user\\Downloads\\output.tif", "w");

        while (nPageIndex < nCount) {
            winrt::Windows::Data::Pdf::PdfPage pdfPage = pdfDocument.GetPage(nPageIndex);

            winrt::Windows::Storage::Streams::IRandomAccessStream outputStream = winrt::Windows::Storage::Streams::InMemoryRandomAccessStream();
            pdfPage.RenderToStreamAsync(outputStream).get();
            pdfPage.Close();

            // 비트맵으로 변환
            auto decoder = winrt::Windows::Graphics::Imaging::BitmapDecoder::CreateAsync(outputStream).get();
            
            uint32_t width, height , faxTiffWidth , faxTiffHeight;

            
            winrt::Windows::Graphics::Imaging::SoftwareBitmap grayBitmap = FloydSteinbergDithering(decoder.GetSoftwareBitmapAsync(
                                                                                            winrt::Windows::Graphics::Imaging::BitmapPixelFormat::Bgra8,
                                                                                            winrt::Windows::Graphics::Imaging::BitmapAlphaMode::Ignore
                                                                                            ).get());

            std::vector<uint8_t> bitmapData = ConvertToWhiteAndBlack(grayBitmap, width ,height , 128);
            std::wcout << bitmapData.size() << std::endl;

            // TIFF 파일 생성
            if (!tiff)
                std::wcout << L"FAILED OPEN TO TIFF FILE" << std::endl;
            
            /*
            TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4); // BGRA (4 channels)
            TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8);
            TIFFSetField(tiff, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
            TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
            TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
            */

            faxTiffWidth = 1728;
            faxTiffHeight = 2292;

            uint32_t rowSize = (width + 7) / 8; //줄당 바이트 수 ( 1비트 씩 )
            uint32_t faxTiffRowSize = (faxTiffWidth + 7) / 8;
            
            if (bitmapData.size() < rowSize * height) {
                std::wcout << "BITMAPDATA SIZE IS INSUFFICIENT FOR THE IMAGE DIMENTIONS" << std::endl;
                return -1;
            }
            
            //이미지 크기를 늘린 데이터를 저장할 Vector
            std::vector<uint8_t> faxTiffData(faxTiffHeight * faxTiffRowSize, 0xFF); // 하얀색으로 채우기
            
            // 확대 비율 계산
            double scaleX = static_cast<double>(width) / faxTiffWidth;
            double scaleY = static_cast<double>(height) / faxTiffHeight;

            // 확대 진행 
            for (uint32_t newY = 0; newY < faxTiffHeight; ++newY) {
                for (uint32_t newX = 0; newX < faxTiffWidth; ++newX) {
                    //원본 이미지에서 대응되는 좌표 계산
                    uint32_t origX = static_cast<uint32_t>(newX * scaleX);
                    uint32_t origY = static_cast<uint32_t>(newY * scaleY);

                    //원본 데이터의 픽셀 값을 가져옴
                    uint32_t origIndex = origY * rowSize + origX / 8;
                    uint8_t origPixel = (bitmapData[origIndex] >> (7 - (origX % 8))) & 1;

                    // vector에 픽셀 값 저장 
                    uint32_t faxTiffIndex = newY * faxTiffRowSize + newX / 8;
                    if (origPixel == 0)
                        faxTiffData[faxTiffIndex] |= (0x80 >> (newX % 8)); //1로 
                    else
                        faxTiffData[faxTiffIndex] &= ~(0x80 >> (newX % 8)); // 0으로

                }
            }


            TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, faxTiffWidth);
            TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, faxTiffHeight);
            TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 1);
            TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 1);
            TIFFSetField(tiff, TIFFTAG_COMPRESSION, COMPRESSION_CCITTFAX4); // CCITT Group 4 압축
            TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE); // 흰색: 1, 검은색: 0
            TIFFSetField(tiff, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB); // 비트 순서
            TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
            TIFFSetField(tiff, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
            TIFFSetField(tiff, TIFFTAG_XRESOLUTION, static_cast<float>(204));
            TIFFSetField(tiff, TIFFTAG_YRESOLUTION, static_cast<float>(196));
            TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);

            for (uint32_t row = 0; row < faxTiffHeight; ++row) 
                TIFFWriteScanline(tiff, &faxTiffData[row * faxTiffRowSize], row, 0);
            
            
            TIFFWriteDirectory(tiff);
            nPageIndex++;
        }

        TIFFClose(tiff);

        std::wcout << "SUCCESS|" << nCount << "|SUCCESS" << std::endl;
    /*
    } catch (hresult_error const& e) {
        std::wcout << "FAIL|" << 0 << "|" << e.message().c_str() << std::endl;
    }
    */
    

}


winrt::Windows::Graphics::Imaging::SoftwareBitmap FloydSteinbergDithering(winrt::Windows::Graphics::Imaging::SoftwareBitmap const& inputBitmap)
{
    if (inputBitmap.BitmapPixelFormat() != winrt::Windows::Graphics::Imaging::BitmapPixelFormat::Bgra8) {
        std::wcout << "FAILED UNSUPPORTED PIXEL FORMAT" << std::endl;
    }

    uint32_t width = inputBitmap.PixelWidth();
    uint32_t height = inputBitmap.PixelHeight();

    winrt::Windows::Graphics::Imaging::SoftwareBitmap resultBitmap(winrt::Windows::Graphics::Imaging::BitmapPixelFormat::Gray8, width, height);

    auto inputBuffer = inputBitmap.LockBuffer(winrt::Windows::Graphics::Imaging::BitmapBufferAccessMode::Read);
    auto outputBuffer = resultBitmap.LockBuffer(winrt::Windows::Graphics::Imaging::BitmapBufferAccessMode::Write);
    
    auto inputReference = inputBuffer.CreateReference();
    auto outputReference = outputBuffer.CreateReference();

    uint8_t* inputData = nullptr;
    uint8_t* outputData = nullptr;
    
    uint32_t inputCapacity = 0;
    uint32_t outputCapacity = 0;

    auto inputByteAccess = inputReference.as<IMemoryBufferByteAccess>();
    inputByteAccess->GetBuffer(&inputData, &inputCapacity);

    auto outputByteAccess = outputReference.as<IMemoryBufferByteAccess>();
    outputByteAccess->GetBuffer(&outputData, &outputCapacity);

    std::vector<float> errorBuffer(width * height, 0.0f);

    for (uint32_t y = 0; y < height; ++y) {
        for (uint32_t x = 0; x < width; ++x) {
            //Bgra8 에서 값 추출
            uint32_t index = (y * width + x) * 4;
            uint8_t blue = inputData[index];
            uint8_t green = inputData[index + 1];
            uint8_t red = inputData[index + 2];

            //밝기 계산
            float originalGray = 0.299f * red + 0.587f * green + 0.114f * blue;
            float correctedGray = originalGray + errorBuffer[y * width + x];

            //디더링 된 값 계산
            uint8_t newPixel = correctedGray >= 128 ? 255 : 0;

            //에러 계산
            float error = correctedGray - newPixel;

            //결과 저장
            outputData[y * width + x] = newPixel;

            //주변 픽셀에 에러 분산
            if (x + 1 < width)
                errorBuffer[y * width + (x + 1)] += error * 7 / 16.0f;
            if (y + 1 < height) {
                if (x > 0) 
                    errorBuffer[(y + 1) * width + (x - 1)] += error * 3 / 16.0f;

                errorBuffer[(y + 1) * width + x] += error * 5 / 16.0f;
                
                if (x + 1 < width)
                    errorBuffer[(y + 1) * width + (x + 1)] += error * 1 / 16.0f;
            }


        }
    }

    return resultBitmap;

}


winrt::Windows::Graphics::Imaging::SoftwareBitmap ConvertGrayScale(winrt::Windows::Graphics::Imaging::SoftwareBitmap const& bitmap) 
{

    if (bitmap.BitmapPixelFormat() != winrt::Windows::Graphics::Imaging::BitmapPixelFormat::Bgra8) {
        std::wcout << "FAILED UNSUPPORTED PIXEL FORMAT" << std::endl;
    }
    
    uint32_t width = bitmap.PixelWidth();
    uint32_t height = bitmap.PixelHeight();

    auto buffer = bitmap.LockBuffer(winrt::Windows::Graphics::Imaging::BitmapBufferAccessMode::Read);

    winrt::Windows::Graphics::Imaging::SoftwareBitmap outBitmap(winrt::Windows::Graphics::Imaging::BitmapPixelFormat::Gray8, width, height);
    auto outBuffer = outBitmap.LockBuffer(winrt::Windows::Graphics::Imaging::BitmapBufferAccessMode::Write);

    auto reference = buffer.CreateReference();
    auto outReference = outBuffer.CreateReference();
    //Microsoft::WRL::ComPtr<IMemoryBufferByteAccess> bufferByteAccess;
    //auto interop = reference.as<IMemoryBufferByteAccess>();
  
    uint8_t* data = nullptr;
    uint32_t capaCity = 0;
    
    uint8_t* outData = nullptr;
    uint32_t outCapaCity = 0;

    //interop->GetBuffer(&data, &capacity);
    auto memBuffer = reference.as<IMemoryBufferByteAccess>();
    memBuffer->GetBuffer(&data, &capaCity);

    auto outMemBuffer = outReference.as<IMemoryBufferByteAccess>();
    outMemBuffer->GetBuffer(&outData, &capaCity);

    //GrayScale 변환
    for (uint32_t y = 0; y < height; ++y) {
        for (uint32_t x = 0; x < width; ++x) {
            uint32_t index = (y * width + x) * 4;
            uint8_t blue = data[index];
            uint8_t green = data[index + 1];
            uint8_t red = data[index + 2];

            //GrayScale 값 계산 ( 가중치 기반 )
            uint8_t gray = static_cast<uint8_t>(0.299 * red + 0.587 * green + 0.114 * blue);

            outData[y * width + x] = gray;
        }
    }


    return outBitmap;

}

std::vector<uint8_t> ConvertToWhiteAndBlack(winrt::Windows::Graphics::Imaging::SoftwareBitmap const& bitmap, uint32_t& width, uint32_t& height, uint8_t threshold) 
{
    if (bitmap.BitmapPixelFormat() != winrt::Windows::Graphics::Imaging::BitmapPixelFormat::Gray8) {
        std::wcout << "FAILED UNSUPPORTED PIXEL FORMAT" << std::endl;
    }


    width = bitmap.PixelWidth();
    height = bitmap.PixelHeight();

    auto buffer = bitmap.LockBuffer(winrt::Windows::Graphics::Imaging::BitmapBufferAccessMode::Read);

    uint8_t* data = nullptr;
    uint32_t capaCity = 0;

    auto reference = buffer.CreateReference();

    auto memBuffer = reference.as<IMemoryBufferByteAccess>();
    memBuffer->GetBuffer(&data, &capaCity);


    uint32_t rowSize = (width + 7) / 8;
    std::vector<uint8_t> bitData(rowSize * height, 0x00);
    winrt::Windows::Graphics::Imaging::BitmapPlaneDescription desc = buffer.GetPlaneDescription(0);


    //픽셀 데이터를 1비트로 변환
    for (uint32_t y = 0; y < desc.Height; ++y) {
        for (uint32_t x = 0; x < desc.Width; ++x) {
            uint32_t grayIndex = y * width + x;
            uint8_t grayValue = data[grayIndex];

            if (grayValue >= threshold)
                bitData[y * rowSize + (x / 8)] |= (0x80 >> (x % 8));
        }
    }

 
    return bitData;
}