/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Convert MacPaint 'PNTG' images.
 */
#include "StdAfx.h"
#include "MacPaint.h"

/*
 * The files are always 576x720 monochrome.
 *
 * This format often starts with a MacBinary header, especially for files
 * posted to BBSs.  The FileType should be 'PNTG', and the CreatorType will
 * usually be 'MPNT'.
 *
 * Following the 128-byte header (or at the very start of the data fork for
 * a file on the Mac) is:
 *
 *  +000-003: 4-byte big-endian version number (0 or 2)
 *  +004-307: pattern data (38 patterns * 8 bytes), not needed to decode image
 *  +308-511: 240 bytes of zeroes, used for padding
 *  +512-nnn: PackBits-encoded scan lines (72 bytes of output each)
 *
 * The files seem to be stored in 512-byte chunks, but it's probably unwise to
 * expect anything other than some extra bytes at the end.
 *
 * The bits are in the same order as Windows BMP, but white/black are reversed
 * (i.e. a '1' bit indicates a black pixel).  This can be accommodated by
 * changing the palette colors or by inverting the bits.
 *
 * Corrupted MacPaint files seem to be popular.  Handling corruption
 * gracefully is important.
 */

/*
 * If the file ends in ".mac", we accept it if it has a MacBinary header or
 * if it begins with 00000002 and exceeds the minimum size required.
 */
void ReformatMacPaint::Examine(ReformatHolder* pHolder)
{
    ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
    const uint8_t* ptr = pHolder->GetSourceBuf(ReformatHolder::kPartData);
    long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
    const char* nameExt = pHolder->GetNameExt();
    long version;

    if (fileLen < kMinSize) {
        LOGI("  MP: not long enough to be MacPaint (%d vs min %d)",
            fileLen, kMinSize);
        goto done;
    } 

    version = Get32BE(ptr);
    if (stricmp(nameExt, ".mac") == 0 && version >= 0 && version <= 3) {
        LOGI("  MP: found w/o MacBinary header");
        applies = ReformatHolder::kApplicProbably;
        goto done;
    }

    version = Get32BE(ptr + 128);
    if (version >= 0 && version <= 3 &&
        ptr[65] == 'P' && ptr[66] == 'N' && ptr[67] == 'T' && ptr[68] == 'G')
    {
        LOGI("  MP: found inside MacBinary header");
        applies = ReformatHolder::kApplicProbably;
        goto done;
    }

done:
    pHolder->SetApplic(ReformatHolder::kReformatMacPaint, applies,
        ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
}

/*
 * Convert the image to a monochrome bitmap.
 */
int ReformatMacPaint::Process(const ReformatHolder* pHolder,
    ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
    ReformatOutput* pOutput)
{
    MyDIBitmap* pDib;
    const uint8_t* srcBuf = pHolder->GetSourceBuf(part);
    long srcLen = pHolder->GetSourceLen(part);
    int retval = -1;

    if (srcLen < kMinSize || srcLen > kMaxSize) {
        LOGI(" MacPaint file is only %d bytes long", srcLen);
        goto bail;
    }

    pDib = ConvertMacPaint(srcBuf, srcLen);
    if (pDib == NULL)
        goto bail;

    SetResultBuffer(pOutput, pDib);
    retval = 0;

bail:
    return retval;
}

/*
 * Handle the actual conversion.
 *
 * 576 pixels per line == 72 bytes per line, which is a multiple of 4 bytes
 * (required for windows BMP).
 */
MyDIBitmap* ReformatMacPaint::ConvertMacPaint(const uint8_t* srcBuf, long length)
{
    MyDIBitmap* pDib = NULL;
    uint8_t* outBuf;
    static const RGBQUAD colorConv[2] = {
        /* blue, green, red, reserved */
        { 0x00, 0x00, 0x00 },   // black
        { 0xff, 0xff, 0xff },   // white
    };
    long version1, version2;
    int offset;

    version1 = Get32BE(srcBuf);
    version2 = Get32BE(srcBuf+128);
    if (version1 >= 0 && version1 <= 3) {
        offset = 0;
    } else if (version2 >= 0 && version2 <= 3 &&
        srcBuf[65] == 'P' && srcBuf[66] == 'N' && srcBuf[67] == 'T' && srcBuf[68] == 'G')
    {
        offset = 128;
    } else {
        LOGI("  MP couldn't determine picture offset!");
        goto bail;
    }
    
    pDib = new MyDIBitmap;
    if (pDib == NULL)
        goto bail;

    srcBuf += offset;
    srcBuf += kLeadingJunkCount;
    length -= offset;
    length -= kLeadingJunkCount;
    LOGI("Adjusted len is %d", length);

    outBuf = (uint8_t*) pDib->Create(kOutputWidth, kOutputHeight,
                                    1, kNumColors);
    if (outBuf == NULL) {
        delete pDib;
        pDib = NULL;
        goto bail;
    }
    pDib->SetColorTable(colorConv);

    uint8_t* outPtr;
    int line;

    /* top row goes at the bottom of the buffer */
    outPtr = outBuf + (kOutputHeight-1) * (kOutputWidth / 8);

    /*
     * Loop through all lines.  When we've output 72 bytes, stop immediately
     * even if we're in a run.  Chances are good that the run was corrupted.
     */
    for (line = 0; line < kOutputHeight; line++) {
        UnPackBits(&srcBuf, &length, &outPtr, 72, 0xff);

        /* back up to start of next line */
        outPtr -= 2*(kOutputWidth / 8);

        if (length < 0)
            break;
    }
    if (length != 0) {
        LOGI("  MP found %d unused bytes at end", length);
    }

bail:
    return pDib;
}
