/*
 * Copyright 2018 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "fuzz/Fuzz.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkStream.h"
#include "include/encode/SkJpegEncoder.h"
#include "include/encode/SkPngEncoder.h"
#include "include/encode/SkWebpEncoder.h"
#include "src/base/SkRandom.h"
#include "src/core/SkOSFile.h"

#include <vector>

// These values were picked arbitrarily to hopefully limit the size of the
// serialized SkPixmaps.
constexpr int MAX_WIDTH = 512;
constexpr int MAX_HEIGHT = 512;

static SkBitmap make_fuzzed_bitmap(Fuzz* fuzz) {
    SkBitmap bm;
    uint32_t w, h;
    fuzz->nextRange(&w, 1, MAX_WIDTH);
    fuzz->nextRange(&h, 1, MAX_HEIGHT);
    if (!bm.tryAllocPixels(SkImageInfo::MakeN32Premul(w, h))) {
        return bm;
    }
    uint32_t n = w * h;
    fuzz->nextN((SkPMColor*)bm.getPixels(), n);
    return bm;
}

DEF_FUZZ(PNGEncoder, fuzz) {
    auto bm = make_fuzzed_bitmap(fuzz);

    auto opts = SkPngEncoder::Options{};
    fuzz->nextRange(&opts.fZLibLevel, 0, 9);

    SkDynamicMemoryWStream dest;
    SkPngEncoder::Encode(&dest, bm.pixmap(), opts);
}

DEF_FUZZ(JPEGEncoder, fuzz) {
    auto bm = make_fuzzed_bitmap(fuzz);

    auto opts = SkJpegEncoder::Options{};
    fuzz->nextRange(&opts.fQuality, 0, 100);

    SkDynamicMemoryWStream dest;
    (void)SkJpegEncoder::Encode(&dest, bm.pixmap(), opts);
}

DEF_FUZZ(WEBPEncoder, fuzz) {
    auto bm = make_fuzzed_bitmap(fuzz);

    auto opts = SkWebpEncoder::Options{};
    fuzz->nextRange(&opts.fQuality, 0.0f, 100.0f);
    bool lossy;
    fuzz->next(&lossy);
    if (lossy) {
        opts.fCompression = SkWebpEncoder::Compression::kLossy;
    } else {
        opts.fCompression = SkWebpEncoder::Compression::kLossless;
    }

    SkDynamicMemoryWStream dest;
    (void)SkWebpEncoder::Encode(&dest, bm.pixmap(), opts);
}

// Not a real fuzz endpoint, but a helper to take in real, good images
// and dump out a corpus for this fuzzer.
DEF_FUZZ(_MakeEncoderCorpus, fuzz) {
    auto bytes = fuzz->fBytes;
    SkDebugf("bytes %zu\n", bytes->size());
    auto img = SkImage::MakeFromEncoded(bytes);
    if (nullptr == img.get()) {
        SkDebugf("invalid image, could not decode\n");
        return;
    }
    if (img->width() > MAX_WIDTH || img->height() > MAX_HEIGHT) {
        SkDebugf("Too big (%d x %d)\n", img->width(), img->height());
        return;
    }
    std::vector<int32_t> dstPixels;
    int rowBytes = img->width() * 4;
    dstPixels.resize(img->height() * rowBytes);
    SkPixmap pm(SkImageInfo::MakeN32Premul(img->width(), img->height()),
        &dstPixels.front(), rowBytes);
    if (!img->readPixels(nullptr, pm, 0, 0)) {
        SkDebugf("Could not read pixmap\n");
        return;
    }

    SkString s("./encoded_corpus/enc_");
    static SkRandom rand;
    s.appendU32(rand.nextU());
    auto file = sk_fopen(s.c_str(), SkFILE_Flags::kWrite_SkFILE_Flag);
    if (!file) {
        SkDebugf("Can't initialize file\n");
        return;
    }
    auto total = pm.info().bytesPerPixel() * pm.width() * pm.height();
    SkDebugf("Writing %d (%d x %d) bytes\n", total, pm.width(), pm.height());
    // Write out the size in two bytes since that's what the fuzzer will
    // read first.
    uint32_t w = pm.width();
    sk_fwrite(&w, sizeof(uint32_t), file);
    uint32_t h = pm.height();
    sk_fwrite(&h, sizeof(uint32_t), file);
    sk_fwrite(pm.addr(), total, file);
    sk_fclose(file);
}
