diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/third_party/skia/tools | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) |
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca
Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/third_party/skia/tools')
146 files changed, 30512 insertions, 0 deletions
diff --git a/chromium/third_party/skia/tools/CopyTilesRenderer.cpp b/chromium/third_party/skia/tools/CopyTilesRenderer.cpp new file mode 100644 index 00000000000..ebd33d88511 --- /dev/null +++ b/chromium/third_party/skia/tools/CopyTilesRenderer.cpp @@ -0,0 +1,101 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "picture_utils.h" +#include "CopyTilesRenderer.h" +#include "SkCanvas.h" +#include "SkDevice.h" +#include "SkImageEncoder.h" +#include "SkPicture.h" +#include "SkPixelRef.h" +#include "SkRect.h" +#include "SkString.h" + +namespace sk_tools { + CopyTilesRenderer::CopyTilesRenderer(int x, int y) + : fXTilesPerLargeTile(x) + , fYTilesPerLargeTile(y) { + } + void CopyTilesRenderer::init(SkPicture* pict, const SkString* writePath, + const SkString* mismatchPath, const SkString* inputFilename, + bool useChecksumBasedFilenames) { + // Do not call INHERITED::init(), which would create a (potentially large) canvas which is + // not used by bench_pictures. + SkASSERT(pict != NULL); + // Only work with absolute widths (as opposed to percentages). + SkASSERT(this->getTileWidth() != 0 && this->getTileHeight() != 0); + fPicture.reset(pict)->ref(); + this->CopyString(&fWritePath, writePath); + this->CopyString(&fMismatchPath, mismatchPath); + this->CopyString(&fInputFilename, inputFilename); + fUseChecksumBasedFilenames = useChecksumBasedFilenames; + this->buildBBoxHierarchy(); + // In order to avoid allocating a large canvas (particularly important for GPU), create one + // canvas that is a multiple of the tile size, and draw portions of the picture. + fLargeTileWidth = fXTilesPerLargeTile * this->getTileWidth(); + fLargeTileHeight = fYTilesPerLargeTile * this->getTileHeight(); + fCanvas.reset(this->INHERITED::setupCanvas(fLargeTileWidth, fLargeTileHeight)); + } + + bool CopyTilesRenderer::render(SkBitmap** out) { + int i = 0; + bool success = true; + SkBitmap dst; + for (int x = 0; x < this->getViewWidth(); x += fLargeTileWidth) { + for (int y = 0; y < this->getViewHeight(); y += fLargeTileHeight) { + SkAutoCanvasRestore autoRestore(fCanvas, true); + // Translate so that we draw the correct portion of the picture. + // Perform a postTranslate so that the scaleFactor does not interfere with the + // positioning. + SkMatrix mat(fCanvas->getTotalMatrix()); + mat.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y)); + fCanvas->setMatrix(mat); + // Draw the picture + fCanvas->drawPicture(fPicture); + // Now extract the picture into tiles + const SkBitmap& baseBitmap = fCanvas->getDevice()->accessBitmap(false); + SkIRect subset; + for (int tileY = 0; tileY < fLargeTileHeight; tileY += this->getTileHeight()) { + for (int tileX = 0; tileX < fLargeTileWidth; tileX += this->getTileWidth()) { + subset.set(tileX, tileY, tileX + this->getTileWidth(), + tileY + this->getTileHeight()); + SkDEBUGCODE(bool extracted =) + baseBitmap.extractSubset(&dst, subset); + SkASSERT(extracted); + if (!fWritePath.isEmpty()) { + // Similar to write() in PictureRenderer.cpp, but just encodes + // a bitmap directly. + // TODO: Share more common code with write() to do this, to properly + // write out the JSON summary, etc. + SkString pathWithNumber = SkOSPath::SkPathJoin(fWritePath.c_str(), + fInputFilename.c_str()); + pathWithNumber.remove(pathWithNumber.size() - 4, 4); + pathWithNumber.appendf("%i.png", i++); + SkBitmap copy; +#if SK_SUPPORT_GPU + if (isUsingGpuDevice()) { + dst.pixelRef()->readPixels(©, &subset); + } else { +#endif + dst.copyTo(©); +#if SK_SUPPORT_GPU + } +#endif + success &= SkImageEncoder::EncodeFile(pathWithNumber.c_str(), copy, + SkImageEncoder::kPNG_Type, 100); + } + } + } + } + } + return success; + } + + SkString CopyTilesRenderer::getConfigNameInternal() { + return SkString("copy_tiles"); + } +} diff --git a/chromium/third_party/skia/tools/CopyTilesRenderer.h b/chromium/third_party/skia/tools/CopyTilesRenderer.h new file mode 100644 index 00000000000..3bf969b15f3 --- /dev/null +++ b/chromium/third_party/skia/tools/CopyTilesRenderer.h @@ -0,0 +1,50 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef CopyTilesRenderer_DEFINED +#define CopyTilesRenderer_DEFINED + +#include "PictureRenderer.h" +#include "SkTypes.h" + +class SkPicture; +class SkString; + +namespace sk_tools { + /** + * PictureRenderer that draws the picture and then extracts it into tiles. For large pictures, + * it will divide the picture into large tiles and draw the picture once for each large tile. + */ + class CopyTilesRenderer : public TiledPictureRenderer { + + public: + CopyTilesRenderer(int x, int y); + virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, + const SkString* inputFilename, + bool useChecksumBasedFilenames) SK_OVERRIDE; + + /** + * Similar to TiledPictureRenderer, this will draw a PNG for each tile. However, the + * numbering (and actual tiles) will be different. + */ + virtual bool render(SkBitmap** out) SK_OVERRIDE; + + virtual bool supportsTimingIndividualTiles() SK_OVERRIDE { return false; } + + private: + int fXTilesPerLargeTile; + int fYTilesPerLargeTile; + + int fLargeTileWidth; + int fLargeTileHeight; + + virtual SkString getConfigNameInternal() SK_OVERRIDE; + + typedef TiledPictureRenderer INHERITED; + }; +} // sk_tools +#endif // CopyTilesRenderer_DEFINED diff --git a/chromium/third_party/skia/tools/CrashHandler.cpp b/chromium/third_party/skia/tools/CrashHandler.cpp new file mode 100644 index 00000000000..ffd9f3bedb0 --- /dev/null +++ b/chromium/third_party/skia/tools/CrashHandler.cpp @@ -0,0 +1,172 @@ +#include "CrashHandler.h" + +#include "SkTypes.h" + +#include <stdlib.h> + +#if defined(SK_BUILD_FOR_MAC) + +// We only use local unwinding, so we can define this to select a faster implementation. +#define UNW_LOCAL_ONLY +#include <libunwind.h> +#include <cxxabi.h> + +static void handler(int sig) { + unw_context_t context; + unw_getcontext(&context); + + unw_cursor_t cursor; + unw_init_local(&cursor, &context); + + SkDebugf("\nSignal %d:\n", sig); + while (unw_step(&cursor) > 0) { + static const size_t kMax = 256; + char mangled[kMax], demangled[kMax]; + unw_word_t offset; + unw_get_proc_name(&cursor, mangled, kMax, &offset); + + int ok; + size_t len = kMax; + abi::__cxa_demangle(mangled, demangled, &len, &ok); + + SkDebugf("%s (+0x%zx)\n", ok == 0 ? demangled : mangled, (size_t)offset); + } + SkDebugf("\n"); + + // Exit NOW. Don't notify other threads, don't call anything registered with atexit(). + _Exit(sig); +} + +#elif defined(SK_BUILD_FOR_UNIX) && !defined(SK_BUILD_FOR_NACL) // NACL doesn't have backtrace(). + +// We'd use libunwind here too, but it's a pain to get installed for both 32 and 64 bit on bots. +// Doesn't matter much: catchsegv is best anyway. +#include <execinfo.h> + +static void handler(int sig) { + static const int kMax = 64; + void* stack[kMax]; + const int count = backtrace(stack, kMax); + + SkDebugf("\nSignal %d:\n", sig); + backtrace_symbols_fd(stack, count, 2/*stderr*/); + + // Exit NOW. Don't notify other threads, don't call anything registered with atexit(). + _Exit(sig); +} + +#endif + +#if defined(SK_BUILD_FOR_MAC) || (defined(SK_BUILD_FOR_UNIX) && !defined(SK_BUILD_FOR_NACL)) +#include <signal.h> + +void SetupCrashHandler() { + static const int kSignals[] = { + SIGABRT, + SIGBUS, + SIGFPE, + SIGILL, + SIGSEGV, + }; + + for (size_t i = 0; i < sizeof(kSignals) / sizeof(kSignals[0]); i++) { + // Register our signal handler unless something's already done so (e.g. catchsegv). + void (*prev)(int) = signal(kSignals[i], handler); + if (prev != SIG_DFL) { + signal(kSignals[i], prev); + } + } +} + +#elif defined(SK_BUILD_FOR_WIN) + +#include <DbgHelp.h> + +static const struct { + const char* name; + int code; +} kExceptions[] = { +#define _(E) {#E, E} + _(EXCEPTION_ACCESS_VIOLATION), + _(EXCEPTION_BREAKPOINT), + _(EXCEPTION_INT_DIVIDE_BY_ZERO), + _(EXCEPTION_STACK_OVERFLOW), + // TODO: more? +#undef _ +}; + +static LONG WINAPI handler(EXCEPTION_POINTERS* e) { + const DWORD code = e->ExceptionRecord->ExceptionCode; + SkDebugf("\nCaught exception %u", code); + for (size_t i = 0; i < SK_ARRAY_COUNT(kExceptions); i++) { + if (kExceptions[i].code == code) { + SkDebugf(" %s", kExceptions[i].name); + } + } + SkDebugf("\n"); + + // We need to run SymInitialize before doing any of the stack walking below. + HANDLE hProcess = GetCurrentProcess(); + SymInitialize(hProcess, 0, true); + + STACKFRAME64 frame; + sk_bzero(&frame, sizeof(frame)); + // Start frame off from the frame that triggered the exception. + CONTEXT* c = e->ContextRecord; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrStack.Mode = AddrModeFlat; + frame.AddrFrame.Mode = AddrModeFlat; +#if defined(_X86_) + frame.AddrPC.Offset = c->Eip; + frame.AddrStack.Offset = c->Esp; + frame.AddrFrame.Offset = c->Ebp; + const DWORD machineType = IMAGE_FILE_MACHINE_I386; +#elif defined(_AMD64_) + frame.AddrPC.Offset = c->Rip; + frame.AddrStack.Offset = c->Rsp; + frame.AddrFrame.Offset = c->Rbp; + const DWORD machineType = IMAGE_FILE_MACHINE_AMD64; +#endif + + while (StackWalk64(machineType, + GetCurrentProcess(), + GetCurrentThread(), + &frame, + c, + NULL, + SymFunctionTableAccess64, + SymGetModuleBase64, + NULL)) { + // Buffer to store symbol name in. + static const int kMaxNameLength = 1024; + uint8_t buffer[sizeof(IMAGEHLP_SYMBOL64) + kMaxNameLength]; + sk_bzero(buffer, sizeof(buffer)); + + // We have to place IMAGEHLP_SYMBOL64 at the front, and fill in how much space it can use. + IMAGEHLP_SYMBOL64* symbol = reinterpret_cast<IMAGEHLP_SYMBOL64*>(&buffer); + symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + symbol->MaxNameLength = kMaxNameLength - 1; + + // Translate the current PC into a symbol and byte offset from the symbol. + DWORD64 offset; + SymGetSymFromAddr64(hProcess, frame.AddrPC.Offset, &offset, symbol); + + SkDebugf("%s +%x\n", symbol->Name, offset); + } + + // Exit NOW. Don't notify other threads, don't call anything registered with atexit(). + _exit(1); + + // The compiler wants us to return something. This is what we'd do if we didn't _exit(). + return EXCEPTION_EXECUTE_HANDLER; +} + +void SetupCrashHandler() { + SetUnhandledExceptionFilter(handler); +} + +#else + +void SetupCrashHandler() { } + +#endif diff --git a/chromium/third_party/skia/tools/CrashHandler.h b/chromium/third_party/skia/tools/CrashHandler.h new file mode 100644 index 00000000000..6c22c8ee705 --- /dev/null +++ b/chromium/third_party/skia/tools/CrashHandler.h @@ -0,0 +1,9 @@ +#ifndef CrashHandler_DEFINED +#define CrashHandler_DEFINED + +// If possible (and not already done) register a handler for a stack trace when we crash. +// Currently this works on Linux and Mac, hopefully Windows soon. +// On Linux, you will get much better results than we can deliver by running "catchsegv <program>". +void SetupCrashHandler(); + +#endif//CrashHandler_DEFINED diff --git a/chromium/third_party/skia/tools/DumpRecord.cpp b/chromium/third_party/skia/tools/DumpRecord.cpp new file mode 100644 index 00000000000..2376fb9cf08 --- /dev/null +++ b/chromium/third_party/skia/tools/DumpRecord.cpp @@ -0,0 +1,113 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <stdio.h> + +#include "SkRecord.h" +#include "SkRecordDraw.h" + +#include "BenchTimer.h" +#include "DumpRecord.h" + +namespace { + +class Dumper { +public: + explicit Dumper(SkCanvas* canvas, int count, bool timeWithCommand) + : fDigits(0) + , fIndent(0) + , fDraw(canvas) + , fTimeWithCommand(timeWithCommand) { + while (count > 0) { + count /= 10; + fDigits++; + } + } + + unsigned index() const { return fDraw.index(); } + void next() { fDraw.next(); } + + template <typename T> + void operator()(const T& command) { + BenchTimer timer; + timer.start(); + fDraw(command); + timer.end(); + + this->print(command, timer.fCpu); + } + + void operator()(const SkRecords::NoOp&) { + // Move on without printing anything. + } + + template <typename T> + void print(const T& command, double time) { + this->printNameAndTime(command, time); + } + + void print(const SkRecords::Restore& command, double time) { + --fIndent; + this->printNameAndTime(command, time); + } + + void print(const SkRecords::Save& command, double time) { + this->printNameAndTime(command, time); + ++fIndent; + } + + void print(const SkRecords::SaveLayer& command, double time) { + this->printNameAndTime(command, time); + ++fIndent; + } + +private: + template <typename T> + void printNameAndTime(const T& command, double time) { + if (!fTimeWithCommand) { + printf("%6.1f ", time * 1000); + } + printf("%*d ", fDigits, fDraw.index()); + for (int i = 0; i < fIndent; i++) { + putchar('\t'); + } + if (fTimeWithCommand) { + printf("%6.1f ", time * 1000); + } + puts(NameOf(command)); + } + + template <typename T> + static const char* NameOf(const T&) { + #define CASE(U) case SkRecords::U##_Type: return #U; + switch(T::kType) { SK_RECORD_TYPES(CASE); } + #undef CASE + SkDEBUGFAIL("Unknown T"); + return "Unknown T"; + } + + static const char* NameOf(const SkRecords::SaveLayer&) { + return "\x1b[31;1mSaveLayer\x1b[0m"; // Bold red. + } + + int fDigits; + int fIndent; + SkRecords::Draw fDraw; + const bool fTimeWithCommand; +}; + +} // namespace + +void DumpRecord(const SkRecord& record, + SkCanvas* canvas, + bool timeWithCommand) { + for (Dumper dumper(canvas, record.count(), timeWithCommand); + dumper.index() < record.count(); + dumper.next()) { + record.visit<void>(dumper.index(), dumper); + } +} diff --git a/chromium/third_party/skia/tools/DumpRecord.h b/chromium/third_party/skia/tools/DumpRecord.h new file mode 100644 index 00000000000..47097472b1c --- /dev/null +++ b/chromium/third_party/skia/tools/DumpRecord.h @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef DumpRecord_DEFINED +#define DumpRecord_DEFINED + +class SkRecord; +class SkCanvas; + +/** + * Draw the record to the supplied canvas via SkRecords::Draw, while + * printing each draw command and run time in microseconds to stdout. + * + * @param timeWithCommand If true, print time next to command, else in + * first column. + */ +void DumpRecord(const SkRecord& record, + SkCanvas* canvas, + bool timeWithCommand); + +#endif // DumpRecord_DEFINED diff --git a/chromium/third_party/skia/tools/LazyDecodeBitmap.cpp b/chromium/third_party/skia/tools/LazyDecodeBitmap.cpp new file mode 100644 index 00000000000..6c4160c09e1 --- /dev/null +++ b/chromium/third_party/skia/tools/LazyDecodeBitmap.cpp @@ -0,0 +1,49 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "LazyDecodeBitmap.h" + +#include "SkData.h" +#include "SkDecodingImageGenerator.h" +#include "SkDiscardableMemoryPool.h" +#include "SkImageGeneratorPriv.h" +#include "SkForceLinking.h" + +#include "SkCommandLineFlags.h" + +__SK_FORCE_IMAGE_DECODER_LINKING; + +DEFINE_bool(useVolatileCache, false, "Use a volatile cache for deferred image decoding pixels. " + "Only meaningful if --deferImageDecoding is set to true and the platform has an " + "implementation."); + +// Fits SkPicture::InstallPixelRefProc call signature. +// Used in SkPicturePlayback::CreateFromStream +bool sk_tools::LazyDecodeBitmap(const void* src, + size_t length, + SkBitmap* dst) { + SkAutoDataUnref data(SkData::NewWithCopy(src, length)); + if (NULL == data.get()) { + return false; + } + + SkAutoTDelete<SkImageGenerator> gen( + SkDecodingImageGenerator::Create( + data, SkDecodingImageGenerator::Options())); + SkImageInfo info; + if ((NULL == gen.get()) || !gen->getInfo(&info)) { + return false; + } + SkDiscardableMemory::Factory* pool = NULL; + if ((!FLAGS_useVolatileCache) || (info.fWidth * info.fHeight < 32 * 1024)) { + // how to do switching with SkDiscardableMemory. + pool = SkGetGlobalDiscardableMemoryPool(); + // Only meaningful if platform has a default discardable + // memory implementation that differs from the global DM pool. + } + return SkInstallDiscardablePixelRef(gen.detach(), dst, pool); +} diff --git a/chromium/third_party/skia/tools/LazyDecodeBitmap.h b/chromium/third_party/skia/tools/LazyDecodeBitmap.h new file mode 100644 index 00000000000..ecceb94f7ed --- /dev/null +++ b/chromium/third_party/skia/tools/LazyDecodeBitmap.h @@ -0,0 +1,24 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef LazyDecodeBitmap_DEFINED +#define LazyDecodeBitmap_DEFINED + +#include "SkTypes.h" + +class SkBitmap; + +namespace sk_tools { + +/** + * Decode the image with DecodeMemoryToTarget but defer the process until it is needed. + */ +bool LazyDecodeBitmap(const void* buffer, size_t size, SkBitmap* bitmap); + +} + +#endif // LazyDecodeBitmap_DEFINED diff --git a/chromium/third_party/skia/tools/OverwriteLine.h b/chromium/third_party/skia/tools/OverwriteLine.h new file mode 100644 index 00000000000..b76c223ba63 --- /dev/null +++ b/chromium/third_party/skia/tools/OverwriteLine.h @@ -0,0 +1,13 @@ +#ifndef OverwriteLine_DEFINED +#define OverwriteLine_DEFINED + +// Print this string to reset and clear your current terminal line. +static const char* kSkOverwriteLine = +#ifdef SK_BUILD_FOR_WIN32 +"\r \r" +#else +"\r\033[K" +#endif +; + +#endif//OverwriteLine_DEFINED diff --git a/chromium/third_party/skia/tools/PdfRenderer.cpp b/chromium/third_party/skia/tools/PdfRenderer.cpp new file mode 100644 index 00000000000..bcecf579a89 --- /dev/null +++ b/chromium/third_party/skia/tools/PdfRenderer.cpp @@ -0,0 +1,60 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "PdfRenderer.h" +#include "SkCanvas.h" +#include "SkDevice.h" +#include "SkPDFDevice.h" +#include "SkPDFDocument.h" + +namespace sk_tools { + +void PdfRenderer::init(SkPicture* pict, SkWStream* stream) { + SkASSERT(NULL == fPicture); + SkASSERT(NULL == fCanvas.get()); + if (fPicture != NULL || NULL != fCanvas.get()) { + return; + } + + SkASSERT(pict != NULL); + if (NULL == pict) { + return; + } + + fPicture = pict; + fCanvas.reset(this->setupCanvas(stream, pict->width(), pict->height())); +} + +SkCanvas* PdfRenderer::setupCanvas(SkWStream* stream, int width, int height) { + fPdfDoc.reset(SkDocument::CreatePDF(stream, NULL, fEncoder)); + + SkCanvas* canvas = fPdfDoc->beginPage(SkIntToScalar(width), SkIntToScalar(height)); + canvas->ref(); + + return canvas; +} + +void PdfRenderer::end() { + fPicture = NULL; + fCanvas.reset(NULL); + fPdfDoc.reset(NULL); +} + +bool SimplePdfRenderer::render() { + SkASSERT(fCanvas.get() != NULL); + SkASSERT(fPicture != NULL); + if (NULL == fCanvas.get() || NULL == fPicture) { + return false; + } + + fCanvas->drawPicture(fPicture); + fCanvas->flush(); + + return fPdfDoc->close(); +} + +} diff --git a/chromium/third_party/skia/tools/PdfRenderer.h b/chromium/third_party/skia/tools/PdfRenderer.h new file mode 100644 index 00000000000..d70c458c748 --- /dev/null +++ b/chromium/third_party/skia/tools/PdfRenderer.h @@ -0,0 +1,67 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef PdfRenderer_DEFINED +#define PdfRenderer_DEFINED + +// +// PdfRender takes a SkPicture and writes it to a PDF file. +// An SkPicture can be built manually, or read from an SKP file. +// + +#include "SkDocument.h" +#include "SkMath.h" +#include "SkPicture.h" +#include "SkTypes.h" +#include "SkTDArray.h" +#include "SkRefCnt.h" +#include "SkString.h" + +class SkBitmap; +class SkCanvas; +class SkWStream; + +namespace sk_tools { + +class PdfRenderer : public SkRefCnt { +public: + virtual void init(SkPicture* pict, SkWStream* stream); + virtual void setup() {} + virtual bool render() = 0; + virtual void end(); + + PdfRenderer(SkPicture::EncodeBitmap encoder) + : fPicture(NULL) + , fEncoder(encoder) + , fPdfDoc(NULL) + {} + +protected: + SkCanvas* setupCanvas(SkWStream* stream, int width, int height); + + SkAutoTUnref<SkCanvas> fCanvas; + SkPicture* fPicture; + SkPicture::EncodeBitmap fEncoder; + SkAutoTUnref<SkDocument> fPdfDoc; + +private: + typedef SkRefCnt INHERITED; +}; + +class SimplePdfRenderer : public PdfRenderer { +public: + SimplePdfRenderer(SkPicture::EncodeBitmap encoder) + : PdfRenderer(encoder) {} + virtual bool render() SK_OVERRIDE; + +private: + typedef PdfRenderer INHERITED; +}; + +} + +#endif // PdfRenderer_DEFINED diff --git a/chromium/third_party/skia/tools/PictureBenchmark.cpp b/chromium/third_party/skia/tools/PictureBenchmark.cpp new file mode 100644 index 00000000000..30967c73814 --- /dev/null +++ b/chromium/third_party/skia/tools/PictureBenchmark.cpp @@ -0,0 +1,265 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "BenchTimer.h" +#include "PictureBenchmark.h" +#include "SkCanvas.h" +#include "SkPicture.h" +#include "SkString.h" +#include "picture_utils.h" + +namespace sk_tools { + +PictureBenchmark::PictureBenchmark() +: fRepeats(1) +, fRenderer(NULL) +, fTimerResult(TimerData::kAvg_Result) +, fTimerTypes(0) +, fTimeIndividualTiles(false) +, fPurgeDecodedTex(false) +, fPreprocess(false) +, fWriter(NULL) +{} + +PictureBenchmark::~PictureBenchmark() { + SkSafeUnref(fRenderer); +} + +void PictureBenchmark::setTimersToShow(bool wall, + bool truncatedWall, + bool cpu, + bool truncatedCpu, + bool gpu) { + fTimerTypes = 0; + fTimerTypes |= wall ? TimerData::kWall_Flag : 0; + fTimerTypes |= truncatedWall ? TimerData::kTruncatedWall_Flag : 0; + fTimerTypes |= cpu ? TimerData::kCpu_Flag : 0; + fTimerTypes |= truncatedCpu ? TimerData::kTruncatedCpu_Flag : 0; + fTimerTypes |= gpu ? TimerData::kGpu_Flag : 0; +} + +BenchTimer* PictureBenchmark::setupTimer(bool useGLTimer) { +#if SK_SUPPORT_GPU + if (useGLTimer && fRenderer != NULL && fRenderer->isUsingGpuDevice()) { + return SkNEW_ARGS(BenchTimer, (fRenderer->getGLContext())); + } +#endif + return SkNEW_ARGS(BenchTimer, (NULL)); +} + +PictureRenderer* PictureBenchmark::setRenderer(sk_tools::PictureRenderer* renderer) { + SkRefCnt_SafeAssign(fRenderer, renderer); + return renderer; +} + +void PictureBenchmark::run(SkPicture* pict) { + SkASSERT(pict); + if (NULL == pict) { + return; + } + + SkASSERT(fRenderer != NULL); + if (NULL == fRenderer) { + return; + } + + fRenderer->init(pict, NULL, NULL, NULL, false); + + // We throw this away to remove first time effects (such as paging in this program) + fRenderer->setup(); + + if (fPreprocess) { + if (NULL != fRenderer->getCanvas()) { + fRenderer->getCanvas()->EXPERIMENTAL_optimize(pict); + } + } + + fRenderer->render(NULL); + fRenderer->resetState(true); // flush, swapBuffers and Finish + + if (fPreprocess) { + if (NULL != fRenderer->getCanvas()) { + fRenderer->getCanvas()->EXPERIMENTAL_purge(pict); + } + } + + if (fPurgeDecodedTex) { + fRenderer->purgeTextures(); + } + + bool usingGpu = false; +#if SK_SUPPORT_GPU + usingGpu = fRenderer->isUsingGpuDevice(); +#endif + + uint32_t timerTypes = fTimerTypes; + if (!usingGpu) { + timerTypes &= ~TimerData::kGpu_Flag; + } + + SkString timeFormat; + if (TimerData::kPerIter_Result == fTimerResult) { + timeFormat = fRenderer->getPerIterTimeFormat(); + } else { + timeFormat = fRenderer->getNormalTimeFormat(); + } + + static const int kNumInnerLoops = 10; + int numOuterLoops = 1; + int numInnerLoops = fRepeats; + + if (TimerData::kPerIter_Result == fTimerResult && fRepeats > 1) { + // interpret this flag combination to mean: generate 'fRepeats' + // numbers by averaging each rendering 'kNumInnerLoops' times + numOuterLoops = fRepeats; + numInnerLoops = kNumInnerLoops; + } + + if (fTimeIndividualTiles) { + TiledPictureRenderer* tiledRenderer = fRenderer->getTiledRenderer(); + SkASSERT(tiledRenderer && tiledRenderer->supportsTimingIndividualTiles()); + if (NULL == tiledRenderer || !tiledRenderer->supportsTimingIndividualTiles()) { + return; + } + int xTiles, yTiles; + if (!tiledRenderer->tileDimensions(xTiles, yTiles)) { + return; + } + + int x, y; + while (tiledRenderer->nextTile(x, y)) { + // There are two timers, which will behave slightly differently: + // 1) longRunningTimer, along with perTileTimerData, will time how long it takes to draw + // one tile fRepeats times, and take the average. As such, it will not respect the + // logPerIter or printMin options, since it does not know the time per iteration. It + // will also be unable to call flush() for each tile. + // The goal of this timer is to make up for a system timer that is not precise enough to + // measure the small amount of time it takes to draw one tile once. + // + // 2) perTileTimer, along with perTileTimerData, will record each run separately, and + // then take the average. As such, it supports logPerIter and printMin options. + // + // Although "legal", having two gpu timers running at the same time + // seems to cause problems (i.e., INVALID_OPERATIONs) on several + // platforms. To work around this, we disable the gpu timer on the + // long running timer. + SkAutoTDelete<BenchTimer> longRunningTimer(this->setupTimer()); + TimerData longRunningTimerData(numOuterLoops); + + for (int outer = 0; outer < numOuterLoops; ++outer) { + SkAutoTDelete<BenchTimer> perTileTimer(this->setupTimer(false)); + TimerData perTileTimerData(numInnerLoops); + + longRunningTimer->start(); + for (int inner = 0; inner < numInnerLoops; ++inner) { + perTileTimer->start(); + tiledRenderer->drawCurrentTile(); + perTileTimer->truncatedEnd(); + tiledRenderer->resetState(false); // flush & swapBuffers, but don't Finish + perTileTimer->end(); + SkAssertResult(perTileTimerData.appendTimes(perTileTimer.get())); + + if (fPurgeDecodedTex) { + fRenderer->purgeTextures(); + } + } + longRunningTimer->truncatedEnd(); + tiledRenderer->resetState(true); // flush, swapBuffers and Finish + longRunningTimer->end(); + SkAssertResult(longRunningTimerData.appendTimes(longRunningTimer.get())); + } + + fWriter->tileConfig(tiledRenderer->getConfigName()); + fWriter->tileMeta(x, y, xTiles, yTiles); + + // TODO(borenet): Turn off per-iteration tile time reporting for now. + // Avoiding logging the time for every iteration for each tile cuts + // down on data file size by a significant amount. Re-enable this once + // we're loading the bench data directly into a data store and are no + // longer generating SVG graphs. +#if 0 + fWriter->tileData( + &perTileTimerData, + timeFormat.c_str(), + fTimerResult, + timerTypes); +#endif + + if (fPurgeDecodedTex) { + fWriter->addTileFlag(PictureResultsWriter::kPurging); + } + fWriter->addTileFlag(PictureResultsWriter::kAvg); + fWriter->tileData( + &longRunningTimerData, + tiledRenderer->getNormalTimeFormat().c_str(), + TimerData::kAvg_Result, + timerTypes, + numInnerLoops); + } + } else { + SkAutoTDelete<BenchTimer> longRunningTimer(this->setupTimer()); + TimerData longRunningTimerData(numOuterLoops); + + for (int outer = 0; outer < numOuterLoops; ++outer) { + SkAutoTDelete<BenchTimer> perRunTimer(this->setupTimer(false)); + TimerData perRunTimerData(numInnerLoops); + + longRunningTimer->start(); + for (int inner = 0; inner < numInnerLoops; ++inner) { + fRenderer->setup(); + + perRunTimer->start(); + fRenderer->render(NULL); + perRunTimer->truncatedEnd(); + fRenderer->resetState(false); // flush & swapBuffers, but don't Finish + perRunTimer->end(); + + SkAssertResult(perRunTimerData.appendTimes(perRunTimer.get())); + + if (fPreprocess) { + if (NULL != fRenderer->getCanvas()) { + fRenderer->getCanvas()->EXPERIMENTAL_purge(pict); + } + } + + if (fPurgeDecodedTex) { + fRenderer->purgeTextures(); + } + } + longRunningTimer->truncatedEnd(); + fRenderer->resetState(true); // flush, swapBuffers and Finish + longRunningTimer->end(); + SkAssertResult(longRunningTimerData.appendTimes(longRunningTimer.get())); + } + + fWriter->tileConfig(fRenderer->getConfigName()); + if (fPurgeDecodedTex) { + fWriter->addTileFlag(PictureResultsWriter::kPurging); + } + + // Beware - since the per-run-timer doesn't ever include a glFinish it can + // report a lower time then the long-running-timer +#if 0 + fWriter->tileData( + &perRunTimerData, + timeFormat.c_str(), + fTimerResult, + timerTypes); +#else + fWriter->tileData( + &longRunningTimerData, + timeFormat.c_str(), + fTimerResult, + timerTypes, + numInnerLoops); +#endif + } + + fRenderer->end(); +} + +} diff --git a/chromium/third_party/skia/tools/PictureBenchmark.h b/chromium/third_party/skia/tools/PictureBenchmark.h new file mode 100644 index 00000000000..142d52685e0 --- /dev/null +++ b/chromium/third_party/skia/tools/PictureBenchmark.h @@ -0,0 +1,75 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef PictureBenchmark_DEFINED +#define PictureBenchmark_DEFINED + +#include "PictureRenderer.h" +#include "PictureResultsWriter.h" +#include "SkTypes.h" +#include "TimerData.h" + +class BenchTimer; +class SkPicture; + +namespace sk_tools { + +class PictureBenchmark { +public: + PictureBenchmark(); + + ~PictureBenchmark(); + + /** + * Draw the provided SkPicture fRepeats times while collecting timing data, and log the output + * via fWriter. + */ + void run(SkPicture* pict); + + void setRepeats(int repeats) { + fRepeats = repeats; + } + + /** + * If true, tells run to log separate timing data for each individual tile. Each tile will be + * drawn fRepeats times. Requires the PictureRenderer set by setRenderer to be a + * TiledPictureRenderer. + */ + void setTimeIndividualTiles(bool indiv) { fTimeIndividualTiles = indiv; } + bool timeIndividualTiles() const { return fTimeIndividualTiles; } + + void setPurgeDecodedTex(bool purgeDecodedTex) { fPurgeDecodedTex = purgeDecodedTex; } + bool purgeDecodedText() const { return fPurgeDecodedTex; } + + void setPreprocess(bool preprocess) { fPreprocess = preprocess; } + bool preprocess() const { return fPreprocess; } + + PictureRenderer* setRenderer(PictureRenderer*); + + void setTimerResultType(TimerData::Result resultType) { fTimerResult = resultType; } + + void setTimersToShow(bool wall, bool truncatedWall, bool cpu, bool truncatedCpu, bool gpu); + + void setWriter(PictureResultsWriter* writer) { fWriter = writer; } + +private: + int fRepeats; + PictureRenderer* fRenderer; + TimerData::Result fTimerResult; + uint32_t fTimerTypes; // bitfield of TimerData::TimerFlags values + bool fTimeIndividualTiles; + bool fPurgeDecodedTex; + bool fPreprocess; + + PictureResultsWriter* fWriter; + + BenchTimer* setupTimer(bool useGLTimer = true); +}; + +} + +#endif // PictureBenchmark_DEFINED diff --git a/chromium/third_party/skia/tools/PictureRenderer.cpp b/chromium/third_party/skia/tools/PictureRenderer.cpp new file mode 100644 index 00000000000..d337a5ebb7d --- /dev/null +++ b/chromium/third_party/skia/tools/PictureRenderer.cpp @@ -0,0 +1,967 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "PictureRenderer.h" +#include "picture_utils.h" +#include "SamplePipeControllers.h" +#include "SkBitmapHasher.h" +#include "SkCanvas.h" +#include "SkData.h" +#include "SkDevice.h" +#include "SkDiscardableMemoryPool.h" +#include "SkGPipe.h" +#if SK_SUPPORT_GPU +#include "gl/GrGLDefines.h" +#include "SkGpuDevice.h" +#endif +#include "SkGraphics.h" +#include "SkImageEncoder.h" +#include "SkMaskFilter.h" +#include "SkMatrix.h" +#include "SkOSFile.h" +#include "SkPicture.h" +#include "SkPictureRecorder.h" +#include "SkPictureUtils.h" +#include "SkPixelRef.h" +#include "SkScalar.h" +#include "SkStream.h" +#include "SkString.h" +#include "SkTemplates.h" +#include "SkTDArray.h" +#include "SkThreadUtils.h" +#include "SkTypes.h" + +static inline SkScalar scalar_log2(SkScalar x) { + static const SkScalar log2_conversion_factor = SkScalarDiv(1, SkScalarLog(2)); + + return SkScalarLog(x) * log2_conversion_factor; +} + +namespace sk_tools { + +enum { + kDefaultTileWidth = 256, + kDefaultTileHeight = 256 +}; + +void PictureRenderer::init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, + const SkString* inputFilename, bool useChecksumBasedFilenames) { + this->CopyString(&fWritePath, writePath); + this->CopyString(&fMismatchPath, mismatchPath); + this->CopyString(&fInputFilename, inputFilename); + fUseChecksumBasedFilenames = useChecksumBasedFilenames; + + SkASSERT(NULL == fPicture); + SkASSERT(NULL == fCanvas.get()); + if (NULL != fPicture || NULL != fCanvas.get()) { + return; + } + + SkASSERT(pict != NULL); + if (NULL == pict) { + return; + } + + fPicture.reset(pict)->ref(); + fCanvas.reset(this->setupCanvas()); +} + +void PictureRenderer::CopyString(SkString* dest, const SkString* src) { + if (NULL != src) { + dest->set(*src); + } else { + dest->reset(); + } +} + +class FlagsDrawFilter : public SkDrawFilter { +public: + FlagsDrawFilter(PictureRenderer::DrawFilterFlags* flags) : + fFlags(flags) {} + + virtual bool filter(SkPaint* paint, Type t) { + paint->setFlags(paint->getFlags() & ~fFlags[t] & SkPaint::kAllFlags); + if (PictureRenderer::kMaskFilter_DrawFilterFlag & fFlags[t]) { + SkMaskFilter* maskFilter = paint->getMaskFilter(); + if (NULL != maskFilter) { + paint->setMaskFilter(NULL); + } + } + if (PictureRenderer::kHinting_DrawFilterFlag & fFlags[t]) { + paint->setHinting(SkPaint::kNo_Hinting); + } else if (PictureRenderer::kSlightHinting_DrawFilterFlag & fFlags[t]) { + paint->setHinting(SkPaint::kSlight_Hinting); + } + return true; + } + +private: + PictureRenderer::DrawFilterFlags* fFlags; +}; + +static void setUpFilter(SkCanvas* canvas, PictureRenderer::DrawFilterFlags* drawFilters) { + if (drawFilters && !canvas->getDrawFilter()) { + canvas->setDrawFilter(SkNEW_ARGS(FlagsDrawFilter, (drawFilters)))->unref(); + if (drawFilters[0] & PictureRenderer::kAAClip_DrawFilterFlag) { + canvas->setAllowSoftClip(false); + } + } +} + +SkCanvas* PictureRenderer::setupCanvas() { + const int width = this->getViewWidth(); + const int height = this->getViewHeight(); + return this->setupCanvas(width, height); +} + +SkCanvas* PictureRenderer::setupCanvas(int width, int height) { + SkCanvas* canvas; + switch(fDeviceType) { + case kBitmap_DeviceType: { + SkBitmap bitmap; + sk_tools::setup_bitmap(&bitmap, width, height); + canvas = SkNEW_ARGS(SkCanvas, (bitmap)); + } + break; +#if SK_SUPPORT_GPU +#if SK_ANGLE + case kAngle_DeviceType: + // fall through +#endif +#if SK_MESA + case kMesa_DeviceType: + // fall through +#endif + case kGPU_DeviceType: + case kNVPR_DeviceType: { + SkAutoTUnref<GrSurface> target; + if (fGrContext) { + // create a render target to back the device + GrTextureDesc desc; + desc.fConfig = kSkia8888_GrPixelConfig; + desc.fFlags = kRenderTarget_GrTextureFlagBit; + desc.fWidth = width; + desc.fHeight = height; + desc.fSampleCnt = fSampleCount; + target.reset(fGrContext->createUncachedTexture(desc, NULL, 0)); + } + if (NULL == target.get()) { + SkASSERT(0); + return NULL; + } + + SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(target)); + canvas = SkNEW_ARGS(SkCanvas, (device.get())); + break; + } +#endif + default: + SkASSERT(0); + return NULL; + } + setUpFilter(canvas, fDrawFilters); + this->scaleToScaleFactor(canvas); + + // Pictures often lie about their extent (i.e., claim to be 100x100 but + // only ever draw to 90x100). Clear here so the undrawn portion will have + // a consistent color + canvas->clear(SK_ColorTRANSPARENT); + return canvas; +} + +void PictureRenderer::scaleToScaleFactor(SkCanvas* canvas) { + SkASSERT(canvas != NULL); + if (fScaleFactor != SK_Scalar1) { + canvas->scale(fScaleFactor, fScaleFactor); + } +} + +void PictureRenderer::end() { + this->resetState(true); + fPicture.reset(NULL); + fCanvas.reset(NULL); +} + +int PictureRenderer::getViewWidth() { + SkASSERT(fPicture != NULL); + int width = SkScalarCeilToInt(fPicture->width() * fScaleFactor); + if (fViewport.width() > 0) { + width = SkMin32(width, fViewport.width()); + } + return width; +} + +int PictureRenderer::getViewHeight() { + SkASSERT(fPicture != NULL); + int height = SkScalarCeilToInt(fPicture->height() * fScaleFactor); + if (fViewport.height() > 0) { + height = SkMin32(height, fViewport.height()); + } + return height; +} + +/** Converts fPicture to a picture that uses a BBoxHierarchy. + * PictureRenderer subclasses that are used to test picture playback + * should call this method during init. + */ +void PictureRenderer::buildBBoxHierarchy() { + SkASSERT(NULL != fPicture); + if (kNone_BBoxHierarchyType != fBBoxHierarchyType && NULL != fPicture) { + SkAutoTDelete<SkBBHFactory> factory(this->getFactory()); + SkPictureRecorder recorder; + SkCanvas* canvas = recorder.beginRecording(fPicture->width(), fPicture->height(), + factory.get(), + this->recordFlags()); + fPicture->draw(canvas); + fPicture.reset(recorder.endRecording()); + } +} + +void PictureRenderer::resetState(bool callFinish) { +#if SK_SUPPORT_GPU + SkGLContextHelper* glContext = this->getGLContext(); + if (NULL == glContext) { + SkASSERT(kBitmap_DeviceType == fDeviceType); + return; + } + + fGrContext->flush(); + glContext->swapBuffers(); + if (callFinish) { + SK_GL(*glContext, Finish()); + } +#endif +} + +void PictureRenderer::purgeTextures() { + SkDiscardableMemoryPool* pool = SkGetGlobalDiscardableMemoryPool(); + + pool->dumpPool(); + +#if SK_SUPPORT_GPU + SkGLContextHelper* glContext = this->getGLContext(); + if (NULL == glContext) { + SkASSERT(kBitmap_DeviceType == fDeviceType); + return; + } + + // resetState should've already done this + fGrContext->flush(); + + fGrContext->purgeAllUnlockedResources(); +#endif +} + +/** + * Write the canvas to an image file and/or JSON summary. + * + * @param canvas Must be non-null. Canvas to be written to a file. + * @param writePath If nonempty, write the binary image to a file within this directory. + * @param mismatchPath If nonempty, write the binary image to a file within this directory, + * but only if the image does not match expectations. + * @param inputFilename If we are writing out a binary image, use this to build its filename. + * @param jsonSummaryPtr If not null, add image results (checksum) to this summary. + * @param useChecksumBasedFilenames If true, use checksum-based filenames when writing to disk. + * @param tileNumberPtr If not null, which tile number this image contains. + * + * @return bool True if the operation completed successfully. + */ +static bool write(SkCanvas* canvas, const SkString& writePath, const SkString& mismatchPath, + const SkString& inputFilename, ImageResultsAndExpectations *jsonSummaryPtr, + bool useChecksumBasedFilenames, const int* tileNumberPtr=NULL) { + SkASSERT(canvas != NULL); + if (NULL == canvas) { + return false; + } + + SkBitmap bitmap; + SkISize size = canvas->getDeviceSize(); + setup_bitmap(&bitmap, size.width(), size.height()); + + canvas->readPixels(&bitmap, 0, 0); + force_all_opaque(bitmap); + BitmapAndDigest bitmapAndDigest(bitmap); + + SkString escapedInputFilename(inputFilename); + replace_char(&escapedInputFilename, '.', '_'); + + // TODO(epoger): what about including the config type within outputFilename? That way, + // we could combine results of different config types without conflicting filenames. + SkString outputFilename; + const char *outputSubdirPtr = NULL; + if (useChecksumBasedFilenames) { + const ImageDigest *imageDigestPtr = bitmapAndDigest.getImageDigestPtr(); + outputSubdirPtr = escapedInputFilename.c_str(); + outputFilename.set(imageDigestPtr->getHashType()); + outputFilename.append("_"); + outputFilename.appendU64(imageDigestPtr->getHashValue()); + } else { + outputFilename.set(escapedInputFilename); + if (NULL != tileNumberPtr) { + outputFilename.append("-tile"); + outputFilename.appendS32(*tileNumberPtr); + } + } + outputFilename.append(".png"); + + if (NULL != jsonSummaryPtr) { + const ImageDigest *imageDigestPtr = bitmapAndDigest.getImageDigestPtr(); + SkString outputRelativePath; + if (outputSubdirPtr) { + outputRelativePath.set(outputSubdirPtr); + outputRelativePath.append("/"); // always use "/", even on Windows + outputRelativePath.append(outputFilename); + } else { + outputRelativePath.set(outputFilename); + } + + jsonSummaryPtr->add(inputFilename.c_str(), outputRelativePath.c_str(), + *imageDigestPtr, tileNumberPtr); + if (!mismatchPath.isEmpty() && + !jsonSummaryPtr->matchesExpectation(inputFilename.c_str(), *imageDigestPtr, + tileNumberPtr)) { + if (!write_bitmap_to_disk(bitmap, mismatchPath, outputSubdirPtr, outputFilename)) { + return false; + } + } + } + + if (writePath.isEmpty()) { + return true; + } else { + return write_bitmap_to_disk(bitmap, writePath, outputSubdirPtr, outputFilename); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +SkCanvas* RecordPictureRenderer::setupCanvas(int width, int height) { + // defer the canvas setup until the render step + return NULL; +} + +// the size_t* parameter is deprecated, so we ignore it +static SkData* encode_bitmap_to_data(size_t*, const SkBitmap& bm) { + return SkImageEncoder::EncodeData(bm, SkImageEncoder::kPNG_Type, 100); +} + +bool RecordPictureRenderer::render(SkBitmap** out) { + SkAutoTDelete<SkBBHFactory> factory(this->getFactory()); + SkPictureRecorder recorder; + SkCanvas* canvas = recorder.beginRecording(this->getViewWidth(), this->getViewHeight(), + factory.get(), + this->recordFlags()); + this->scaleToScaleFactor(canvas); + fPicture->draw(canvas); + SkAutoTUnref<SkPicture> picture(recorder.endRecording()); + if (!fWritePath.isEmpty()) { + // Record the new picture as a new SKP with PNG encoded bitmaps. + SkString skpPath = SkOSPath::SkPathJoin(fWritePath.c_str(), fInputFilename.c_str()); + SkFILEWStream stream(skpPath.c_str()); + picture->serialize(&stream, &encode_bitmap_to_data); + return true; + } + return false; +} + +SkString RecordPictureRenderer::getConfigNameInternal() { + return SkString("record"); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +bool PipePictureRenderer::render(SkBitmap** out) { + SkASSERT(fCanvas.get() != NULL); + SkASSERT(fPicture != NULL); + if (NULL == fCanvas.get() || NULL == fPicture) { + return false; + } + + PipeController pipeController(fCanvas.get()); + SkGPipeWriter writer; + SkCanvas* pipeCanvas = writer.startRecording(&pipeController); + pipeCanvas->drawPicture(fPicture); + writer.endRecording(); + fCanvas->flush(); + if (NULL != out) { + *out = SkNEW(SkBitmap); + setup_bitmap(*out, fPicture->width(), fPicture->height()); + fCanvas->readPixels(*out, 0, 0); + } + if (fEnableWrites) { + return write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr, + fUseChecksumBasedFilenames); + } else { + return true; + } +} + +SkString PipePictureRenderer::getConfigNameInternal() { + return SkString("pipe"); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +void SimplePictureRenderer::init(SkPicture* picture, const SkString* writePath, + const SkString* mismatchPath, const SkString* inputFilename, + bool useChecksumBasedFilenames) { + INHERITED::init(picture, writePath, mismatchPath, inputFilename, useChecksumBasedFilenames); + this->buildBBoxHierarchy(); +} + +bool SimplePictureRenderer::render(SkBitmap** out) { + SkASSERT(fCanvas.get() != NULL); + SkASSERT(NULL != fPicture); + if (NULL == fCanvas.get() || NULL == fPicture) { + return false; + } + + fCanvas->drawPicture(fPicture); + fCanvas->flush(); + if (NULL != out) { + *out = SkNEW(SkBitmap); + setup_bitmap(*out, fPicture->width(), fPicture->height()); + fCanvas->readPixels(*out, 0, 0); + } + if (fEnableWrites) { + return write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr, + fUseChecksumBasedFilenames); + } else { + return true; + } +} + +SkString SimplePictureRenderer::getConfigNameInternal() { + return SkString("simple"); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +TiledPictureRenderer::TiledPictureRenderer() + : fTileWidth(kDefaultTileWidth) + , fTileHeight(kDefaultTileHeight) + , fTileWidthPercentage(0.0) + , fTileHeightPercentage(0.0) + , fTileMinPowerOf2Width(0) + , fCurrentTileOffset(-1) + , fTilesX(0) + , fTilesY(0) { } + +void TiledPictureRenderer::init(SkPicture* pict, const SkString* writePath, + const SkString* mismatchPath, const SkString* inputFilename, + bool useChecksumBasedFilenames) { + SkASSERT(NULL != pict); + SkASSERT(0 == fTileRects.count()); + if (NULL == pict || fTileRects.count() != 0) { + return; + } + + // Do not call INHERITED::init(), which would create a (potentially large) canvas which is not + // used by bench_pictures. + fPicture.reset(pict)->ref(); + this->CopyString(&fWritePath, writePath); + this->CopyString(&fMismatchPath, mismatchPath); + this->CopyString(&fInputFilename, inputFilename); + fUseChecksumBasedFilenames = useChecksumBasedFilenames; + this->buildBBoxHierarchy(); + + if (fTileWidthPercentage > 0) { + fTileWidth = sk_float_ceil2int(float(fTileWidthPercentage * fPicture->width() / 100)); + } + if (fTileHeightPercentage > 0) { + fTileHeight = sk_float_ceil2int(float(fTileHeightPercentage * fPicture->height() / 100)); + } + + if (fTileMinPowerOf2Width > 0) { + this->setupPowerOf2Tiles(); + } else { + this->setupTiles(); + } + fCanvas.reset(this->setupCanvas(fTileWidth, fTileHeight)); + // Initialize to -1 so that the first call to nextTile will set this up to draw tile 0 on the + // first call to drawCurrentTile. + fCurrentTileOffset = -1; +} + +void TiledPictureRenderer::end() { + fTileRects.reset(); + this->INHERITED::end(); +} + +void TiledPictureRenderer::setupTiles() { + // Only use enough tiles to cover the viewport + const int width = this->getViewWidth(); + const int height = this->getViewHeight(); + + fTilesX = fTilesY = 0; + for (int tile_y_start = 0; tile_y_start < height; tile_y_start += fTileHeight) { + fTilesY++; + for (int tile_x_start = 0; tile_x_start < width; tile_x_start += fTileWidth) { + if (0 == tile_y_start) { + // Only count tiles in the X direction on the first pass. + fTilesX++; + } + *fTileRects.append() = SkRect::MakeXYWH(SkIntToScalar(tile_x_start), + SkIntToScalar(tile_y_start), + SkIntToScalar(fTileWidth), + SkIntToScalar(fTileHeight)); + } + } +} + +bool TiledPictureRenderer::tileDimensions(int &x, int &y) { + if (fTileRects.count() == 0 || NULL == fPicture) { + return false; + } + x = fTilesX; + y = fTilesY; + return true; +} + +// The goal of the powers of two tiles is to minimize the amount of wasted tile +// space in the width-wise direction and then minimize the number of tiles. The +// constraints are that every tile must have a pixel width that is a power of +// two and also be of some minimal width (that is also a power of two). +// +// This is solved by first taking our picture size and rounding it up to the +// multiple of the minimal width. The binary representation of this rounded +// value gives us the tiles we need: a bit of value one means we need a tile of +// that size. +void TiledPictureRenderer::setupPowerOf2Tiles() { + // Only use enough tiles to cover the viewport + const int width = this->getViewWidth(); + const int height = this->getViewHeight(); + + int rounded_value = width; + if (width % fTileMinPowerOf2Width != 0) { + rounded_value = width - (width % fTileMinPowerOf2Width) + fTileMinPowerOf2Width; + } + + int num_bits = SkScalarCeilToInt(scalar_log2(SkIntToScalar(width))); + int largest_possible_tile_size = 1 << num_bits; + + fTilesX = fTilesY = 0; + // The tile height is constant for a particular picture. + for (int tile_y_start = 0; tile_y_start < height; tile_y_start += fTileHeight) { + fTilesY++; + int tile_x_start = 0; + int current_width = largest_possible_tile_size; + // Set fTileWidth to be the width of the widest tile, so that each canvas is large enough + // to draw each tile. + fTileWidth = current_width; + + while (current_width >= fTileMinPowerOf2Width) { + // It is very important this is a bitwise AND. + if (current_width & rounded_value) { + if (0 == tile_y_start) { + // Only count tiles in the X direction on the first pass. + fTilesX++; + } + *fTileRects.append() = SkRect::MakeXYWH(SkIntToScalar(tile_x_start), + SkIntToScalar(tile_y_start), + SkIntToScalar(current_width), + SkIntToScalar(fTileHeight)); + tile_x_start += current_width; + } + + current_width >>= 1; + } + } +} + +/** + * Draw the specified picture to the canvas translated to rectangle provided, so that this mini + * canvas represents the rectangle's portion of the overall picture. + * Saves and restores so that the initial clip and matrix return to their state before this function + * is called. + */ +static void draw_tile_to_canvas(SkCanvas* canvas, const SkRect& tileRect, SkPicture* picture) { + int saveCount = canvas->save(); + // Translate so that we draw the correct portion of the picture. + // Perform a postTranslate so that the scaleFactor does not interfere with the positioning. + SkMatrix mat(canvas->getTotalMatrix()); + mat.postTranslate(-tileRect.fLeft, -tileRect.fTop); + canvas->setMatrix(mat); + canvas->drawPicture(picture); + canvas->restoreToCount(saveCount); + canvas->flush(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Copies the entirety of the src bitmap (typically a tile) into a portion of the dst bitmap. + * If the src bitmap is too large to fit within the dst bitmap after the x and y + * offsets have been applied, any excess will be ignored (so only the top-left portion of the + * src bitmap will be copied). + * + * @param src source bitmap + * @param dst destination bitmap + * @param xOffset x-offset within destination bitmap + * @param yOffset y-offset within destination bitmap + */ +static void bitmapCopyAtOffset(const SkBitmap& src, SkBitmap* dst, + int xOffset, int yOffset) { + for (int y = 0; y <src.height() && y + yOffset < dst->height() ; y++) { + for (int x = 0; x < src.width() && x + xOffset < dst->width() ; x++) { + *dst->getAddr32(xOffset + x, yOffset + y) = *src.getAddr32(x, y); + } + } +} + +bool TiledPictureRenderer::nextTile(int &i, int &j) { + if (++fCurrentTileOffset < fTileRects.count()) { + i = fCurrentTileOffset % fTilesX; + j = fCurrentTileOffset / fTilesX; + return true; + } + return false; +} + +void TiledPictureRenderer::drawCurrentTile() { + SkASSERT(fCurrentTileOffset >= 0 && fCurrentTileOffset < fTileRects.count()); + draw_tile_to_canvas(fCanvas, fTileRects[fCurrentTileOffset], fPicture); +} + +bool TiledPictureRenderer::render(SkBitmap** out) { + SkASSERT(fPicture != NULL); + if (NULL == fPicture) { + return false; + } + + SkBitmap bitmap; + if (out){ + *out = SkNEW(SkBitmap); + setup_bitmap(*out, fPicture->width(), fPicture->height()); + setup_bitmap(&bitmap, fTileWidth, fTileHeight); + } + bool success = true; + for (int i = 0; i < fTileRects.count(); ++i) { + draw_tile_to_canvas(fCanvas, fTileRects[i], fPicture); + if (fEnableWrites) { + success &= write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr, + fUseChecksumBasedFilenames, &i); + } + if (NULL != out) { + if (fCanvas->readPixels(&bitmap, 0, 0)) { + // Add this tile to the entire bitmap. + bitmapCopyAtOffset(bitmap, *out, SkScalarFloorToInt(fTileRects[i].left()), + SkScalarFloorToInt(fTileRects[i].top())); + } else { + success = false; + } + } + } + return success; +} + +SkCanvas* TiledPictureRenderer::setupCanvas(int width, int height) { + SkCanvas* canvas = this->INHERITED::setupCanvas(width, height); + SkASSERT(NULL != fPicture); + // Clip the tile to an area that is completely inside both the SkPicture and the viewport. This + // is mostly important for tiles on the right and bottom edges as they may go over this area and + // the picture may have some commands that draw outside of this area and so should not actually + // be written. + // Uses a clipRegion so that it will be unaffected by the scale factor, which may have been set + // by INHERITED::setupCanvas. + SkRegion clipRegion; + clipRegion.setRect(0, 0, this->getViewWidth(), this->getViewHeight()); + canvas->clipRegion(clipRegion); + return canvas; +} + +SkString TiledPictureRenderer::getConfigNameInternal() { + SkString name; + if (fTileMinPowerOf2Width > 0) { + name.append("pow2tile_"); + name.appendf("%i", fTileMinPowerOf2Width); + } else { + name.append("tile_"); + if (fTileWidthPercentage > 0) { + name.appendf("%.f%%", fTileWidthPercentage); + } else { + name.appendf("%i", fTileWidth); + } + } + name.append("x"); + if (fTileHeightPercentage > 0) { + name.appendf("%.f%%", fTileHeightPercentage); + } else { + name.appendf("%i", fTileHeight); + } + return name; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +// Holds all of the information needed to draw a set of tiles. +class CloneData : public SkRunnable { + +public: + CloneData(SkPicture* clone, SkCanvas* canvas, SkTDArray<SkRect>& rects, int start, int end, + SkRunnable* done, ImageResultsAndExpectations* jsonSummaryPtr, + bool useChecksumBasedFilenames, bool enableWrites) + : fClone(clone) + , fCanvas(canvas) + , fEnableWrites(enableWrites) + , fRects(rects) + , fStart(start) + , fEnd(end) + , fSuccess(NULL) + , fDone(done) + , fJsonSummaryPtr(jsonSummaryPtr) + , fUseChecksumBasedFilenames(useChecksumBasedFilenames) { + SkASSERT(fDone != NULL); + } + + virtual void run() SK_OVERRIDE { + SkGraphics::SetTLSFontCacheLimit(1024 * 1024); + + SkBitmap bitmap; + if (fBitmap != NULL) { + // All tiles are the same size. + setup_bitmap(&bitmap, SkScalarFloorToInt(fRects[0].width()), SkScalarFloorToInt(fRects[0].height())); + } + + for (int i = fStart; i < fEnd; i++) { + draw_tile_to_canvas(fCanvas, fRects[i], fClone); + if (fEnableWrites) { + if (!write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr, + fUseChecksumBasedFilenames, &i) + && fSuccess != NULL) { + *fSuccess = false; + // If one tile fails to write to a file, do not continue drawing the rest. + break; + } + if (fBitmap != NULL) { + if (fCanvas->readPixels(&bitmap, 0, 0)) { + SkAutoLockPixels alp(*fBitmap); + bitmapCopyAtOffset(bitmap, fBitmap, SkScalarFloorToInt(fRects[i].left()), + SkScalarFloorToInt(fRects[i].top())); + } else { + *fSuccess = false; + // If one tile fails to read pixels, do not continue drawing the rest. + break; + } + } + } + } + fDone->run(); + } + + void setPathsAndSuccess(const SkString& writePath, const SkString& mismatchPath, + const SkString& inputFilename, bool* success) { + fWritePath.set(writePath); + fMismatchPath.set(mismatchPath); + fInputFilename.set(inputFilename); + fSuccess = success; + } + + void setBitmap(SkBitmap* bitmap) { + fBitmap = bitmap; + } + +private: + // All pointers unowned. + SkPicture* fClone; // Picture to draw from. Each CloneData has a unique one which + // is threadsafe. + SkCanvas* fCanvas; // Canvas to draw to. Reused for each tile. + bool fEnableWrites; // TODO(epoger): Temporary hack; see declaration of + // fEnableWrites in PictureRenderer.h. + SkString fWritePath; // If not empty, write all results into this directory. + SkString fMismatchPath; // If not empty, write all unexpected results into this dir. + SkString fInputFilename; // Filename of input SkPicture file. + SkTDArray<SkRect>& fRects; // All tiles of the picture. + const int fStart; // Range of tiles drawn by this thread. + const int fEnd; + bool* fSuccess; // Only meaningful if path is non-null. Shared by all threads, + // and only set to false upon failure to write to a PNG. + SkRunnable* fDone; + SkBitmap* fBitmap; + ImageResultsAndExpectations* fJsonSummaryPtr; + bool fUseChecksumBasedFilenames; +}; + +MultiCorePictureRenderer::MultiCorePictureRenderer(int threadCount) +: fNumThreads(threadCount) +, fThreadPool(threadCount) +, fCountdown(threadCount) { + // Only need to create fNumThreads - 1 clones, since one thread will use the base + // picture. + fPictureClones = SkNEW_ARRAY(SkPicture, fNumThreads - 1); + fCloneData = SkNEW_ARRAY(CloneData*, fNumThreads); +} + +void MultiCorePictureRenderer::init(SkPicture *pict, const SkString* writePath, + const SkString* mismatchPath, const SkString* inputFilename, + bool useChecksumBasedFilenames) { + // Set fPicture and the tiles. + this->INHERITED::init(pict, writePath, mismatchPath, inputFilename, useChecksumBasedFilenames); + for (int i = 0; i < fNumThreads; ++i) { + *fCanvasPool.append() = this->setupCanvas(this->getTileWidth(), this->getTileHeight()); + } + // Only need to create fNumThreads - 1 clones, since one thread will use the base picture. + fPicture->clone(fPictureClones, fNumThreads - 1); + // Populate each thread with the appropriate data. + // Group the tiles into nearly equal size chunks, rounding up so we're sure to cover them all. + const int chunkSize = (fTileRects.count() + fNumThreads - 1) / fNumThreads; + + for (int i = 0; i < fNumThreads; i++) { + SkPicture* pic; + if (i == fNumThreads-1) { + // The last set will use the original SkPicture. + pic = fPicture; + } else { + pic = &fPictureClones[i]; + } + const int start = i * chunkSize; + const int end = SkMin32(start + chunkSize, fTileRects.count()); + fCloneData[i] = SkNEW_ARGS(CloneData, + (pic, fCanvasPool[i], fTileRects, start, end, &fCountdown, + fJsonSummaryPtr, useChecksumBasedFilenames, fEnableWrites)); + } +} + +bool MultiCorePictureRenderer::render(SkBitmap** out) { + bool success = true; + if (!fWritePath.isEmpty() || !fMismatchPath.isEmpty()) { + for (int i = 0; i < fNumThreads-1; i++) { + fCloneData[i]->setPathsAndSuccess(fWritePath, fMismatchPath, fInputFilename, &success); + } + } + + if (NULL != out) { + *out = SkNEW(SkBitmap); + setup_bitmap(*out, fPicture->width(), fPicture->height()); + for (int i = 0; i < fNumThreads; i++) { + fCloneData[i]->setBitmap(*out); + } + } else { + for (int i = 0; i < fNumThreads; i++) { + fCloneData[i]->setBitmap(NULL); + } + } + + fCountdown.reset(fNumThreads); + for (int i = 0; i < fNumThreads; i++) { + fThreadPool.add(fCloneData[i]); + } + fCountdown.wait(); + + return success; +} + +void MultiCorePictureRenderer::end() { + for (int i = 0; i < fNumThreads - 1; i++) { + SkDELETE(fCloneData[i]); + fCloneData[i] = NULL; + } + + fCanvasPool.unrefAll(); + + this->INHERITED::end(); +} + +MultiCorePictureRenderer::~MultiCorePictureRenderer() { + // Each individual CloneData was deleted in end. + SkDELETE_ARRAY(fCloneData); + SkDELETE_ARRAY(fPictureClones); +} + +SkString MultiCorePictureRenderer::getConfigNameInternal() { + SkString name = this->INHERITED::getConfigNameInternal(); + name.appendf("_multi_%i_threads", fNumThreads); + return name; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +void PlaybackCreationRenderer::setup() { + SkAutoTDelete<SkBBHFactory> factory(this->getFactory()); + fRecorder.reset(SkNEW(SkPictureRecorder)); + SkCanvas* canvas = fRecorder->beginRecording(this->getViewWidth(), this->getViewHeight(), + factory.get(), + this->recordFlags()); + this->scaleToScaleFactor(canvas); + canvas->drawPicture(fPicture); +} + +bool PlaybackCreationRenderer::render(SkBitmap** out) { + fPicture.reset(fRecorder->endRecording()); + // Since this class does not actually render, return false. + return false; +} + +SkString PlaybackCreationRenderer::getConfigNameInternal() { + return SkString("playback_creation"); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// SkPicture variants for each BBoxHierarchy type + +SkBBHFactory* PictureRenderer::getFactory() { + switch (fBBoxHierarchyType) { + case kNone_BBoxHierarchyType: + return NULL; + case kQuadTree_BBoxHierarchyType: + return SkNEW(SkQuadTreeFactory); + case kRTree_BBoxHierarchyType: + return SkNEW(SkRTreeFactory); + case kTileGrid_BBoxHierarchyType: + return SkNEW_ARGS(SkTileGridFactory, (fGridInfo)); + } + SkASSERT(0); // invalid bbhType + return NULL; +} + +/////////////////////////////////////////////////////////////////////////////// + +class GatherRenderer : public PictureRenderer { +public: + virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE { + SkRect bounds = SkRect::MakeWH(SkIntToScalar(fPicture->width()), + SkIntToScalar(fPicture->height())); + SkData* data = SkPictureUtils::GatherPixelRefs(fPicture, bounds); + SkSafeUnref(data); + + return (fWritePath.isEmpty()); // we don't have anything to write + } + +private: + virtual SkString getConfigNameInternal() SK_OVERRIDE { + return SkString("gather_pixelrefs"); + } +}; + +PictureRenderer* CreateGatherPixelRefsRenderer() { + return SkNEW(GatherRenderer); +} + +/////////////////////////////////////////////////////////////////////////////// + +class PictureCloneRenderer : public PictureRenderer { +public: + virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE { + for (int i = 0; i < 100; ++i) { + SkPicture* clone = fPicture->clone(); + SkSafeUnref(clone); + } + + return (fWritePath.isEmpty()); // we don't have anything to write + } + +private: + virtual SkString getConfigNameInternal() SK_OVERRIDE { + return SkString("picture_clone"); + } +}; + +PictureRenderer* CreatePictureCloneRenderer() { + return SkNEW(PictureCloneRenderer); +} + +} // namespace sk_tools diff --git a/chromium/third_party/skia/tools/PictureRenderer.h b/chromium/third_party/skia/tools/PictureRenderer.h new file mode 100644 index 00000000000..efe118ff08f --- /dev/null +++ b/chromium/third_party/skia/tools/PictureRenderer.h @@ -0,0 +1,661 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef PictureRenderer_DEFINED +#define PictureRenderer_DEFINED + +#include "SkCanvas.h" +#include "SkCountdown.h" +#include "SkDrawFilter.h" +#include "SkMath.h" +#include "SkPaint.h" +#include "SkPicture.h" +#include "SkPictureRecorder.h" +#include "SkRect.h" +#include "SkRefCnt.h" +#include "SkRunnable.h" +#include "SkString.h" +#include "SkTDArray.h" +#include "SkThreadPool.h" +#include "SkTypes.h" + +#if SK_SUPPORT_GPU +#include "GrContextFactory.h" +#include "GrContext.h" +#endif + +#include "image_expectations.h" + +class SkBitmap; +class SkCanvas; +class SkGLContextHelper; +class SkThread; + +namespace sk_tools { + +class TiledPictureRenderer; + +class PictureRenderer : public SkRefCnt { + +public: + enum SkDeviceTypes { +#if SK_ANGLE + kAngle_DeviceType, +#endif +#if SK_MESA + kMesa_DeviceType, +#endif + kBitmap_DeviceType, +#if SK_SUPPORT_GPU + kGPU_DeviceType, + kNVPR_DeviceType, +#endif + }; + + enum BBoxHierarchyType { + kNone_BBoxHierarchyType = 0, + kQuadTree_BBoxHierarchyType, + kRTree_BBoxHierarchyType, + kTileGrid_BBoxHierarchyType, + + kLast_BBoxHierarchyType = kTileGrid_BBoxHierarchyType, + }; + + // this uses SkPaint::Flags as a base and adds additional flags + enum DrawFilterFlags { + kNone_DrawFilterFlag = 0, + kHinting_DrawFilterFlag = 0x10000, // toggles between no hinting and normal hinting + kSlightHinting_DrawFilterFlag = 0x20000, // toggles between slight and normal hinting + kAAClip_DrawFilterFlag = 0x40000, // toggles between soft and hard clip + kMaskFilter_DrawFilterFlag = 0x80000, // toggles on/off mask filters (e.g., blurs) + }; + + SK_COMPILE_ASSERT(!(kMaskFilter_DrawFilterFlag & SkPaint::kAllFlags), maskfilter_flag_must_be_greater); + SK_COMPILE_ASSERT(!(kHinting_DrawFilterFlag & SkPaint::kAllFlags), + hinting_flag_must_be_greater); + SK_COMPILE_ASSERT(!(kSlightHinting_DrawFilterFlag & SkPaint::kAllFlags), + slight_hinting_flag_must_be_greater); + + /** + * Called with each new SkPicture to render. + * + * @param pict The SkPicture to render. + * @param writePath The output directory within which this renderer should write all images, + * or NULL if this renderer should not write all images. + * @param mismatchPath The output directory within which this renderer should write any images + * which do not match expectations, or NULL if this renderer should not write mismatches. + * @param inputFilename The name of the input file we are rendering. + * @param useChecksumBasedFilenames Whether to use checksum-based filenames when writing + * bitmap images to disk. + */ + virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, + const SkString* inputFilename, bool useChecksumBasedFilenames); + + /** + * TODO(epoger): Temporary hack, while we work on http://skbug.com/2584 ('bench_pictures is + * timing reading pixels and writing json files'), such that: + * - render_pictures can call this method and continue to work + * - any other callers (bench_pictures) will skip calls to write() by default + */ + void enableWrites() { fEnableWrites = true; } + + /** + * Set the viewport so that only the portion listed gets drawn. + */ + void setViewport(SkISize size) { fViewport = size; } + + /** + * Set the scale factor at which draw the picture. + */ + void setScaleFactor(SkScalar scale) { fScaleFactor = scale; } + + /** + * Perform any setup that should done prior to each iteration of render() which should not be + * timed. + */ + virtual void setup() {} + + /** + * Perform the work. If this is being called within the context of bench_pictures, + * this is the step that will be timed. + * + * Typically "the work" is rendering an SkPicture into a bitmap, but in some subclasses + * it is recording the source SkPicture into another SkPicture. + * + * If fWritePath has been specified, the result of the work will be written to that dir. + * If fMismatchPath has been specified, and the actual image result differs from its + * expectation, the result of the work will be written to that dir. + * + * @param out If non-null, the implementing subclass MAY allocate an SkBitmap, copy the + * output image into it, and return it here. (Some subclasses ignore this parameter) + * @return bool True if rendering succeeded and, if fWritePath had been specified, the output + * was successfully written to a file. + */ + virtual bool render(SkBitmap** out = NULL) = 0; + + /** + * Called once finished with a particular SkPicture, before calling init again, and before + * being done with this Renderer. + */ + virtual void end(); + + /** + * If this PictureRenderer is actually a TiledPictureRender, return a pointer to this as a + * TiledPictureRender so its methods can be called. + */ + virtual TiledPictureRenderer* getTiledRenderer() { return NULL; } + + /** + * Resets the GPU's state. Does nothing if the backing is raster. For a GPU renderer, calls + * flush, swapBuffers and, if callFinish is true, finish. + * @param callFinish Whether to call finish. + */ + void resetState(bool callFinish); + + /** + * Remove all decoded textures from the CPU caches and all uploaded textures + * from the GPU. + */ + void purgeTextures(); + + /** + * Set the backend type. Returns true on success and false on failure. + */ + bool setDeviceType(SkDeviceTypes deviceType) { + fDeviceType = deviceType; +#if SK_SUPPORT_GPU + // In case this function is called more than once + SkSafeUnref(fGrContext); + fGrContext = NULL; + // Set to Native so it will have an initial value. + GrContextFactory::GLContextType glContextType = GrContextFactory::kNative_GLContextType; +#endif + switch(deviceType) { + case kBitmap_DeviceType: + return true; +#if SK_SUPPORT_GPU + case kGPU_DeviceType: + // Already set to GrContextFactory::kNative_GLContextType, above. + break; + case kNVPR_DeviceType: + glContextType = GrContextFactory::kNVPR_GLContextType; + break; +#if SK_ANGLE + case kAngle_DeviceType: + glContextType = GrContextFactory::kANGLE_GLContextType; + break; +#endif +#if SK_MESA + case kMesa_DeviceType: + glContextType = GrContextFactory::kMESA_GLContextType; + break; +#endif +#endif + default: + // Invalid device type. + return false; + } +#if SK_SUPPORT_GPU + fGrContext = fGrContextFactory.get(glContextType); + if (NULL == fGrContext) { + return false; + } else { + fGrContext->ref(); + return true; + } +#endif + } + +#if SK_SUPPORT_GPU + void setSampleCount(int sampleCount) { + fSampleCount = sampleCount; + } +#endif + + void setDrawFilters(DrawFilterFlags const * const filters, const SkString& configName) { + memcpy(fDrawFilters, filters, sizeof(fDrawFilters)); + fDrawFiltersConfig = configName; + } + + void setBBoxHierarchyType(BBoxHierarchyType bbhType) { + fBBoxHierarchyType = bbhType; + } + + BBoxHierarchyType getBBoxHierarchyType() { return fBBoxHierarchyType; } + + void setGridSize(int width, int height) { + fGridInfo.fTileInterval.set(width, height); + } + + void setJsonSummaryPtr(ImageResultsAndExpectations* jsonSummaryPtr) { + fJsonSummaryPtr = jsonSummaryPtr; + } + + bool isUsingBitmapDevice() { + return kBitmap_DeviceType == fDeviceType; + } + + virtual SkString getPerIterTimeFormat() { return SkString("%.2f"); } + + virtual SkString getNormalTimeFormat() { return SkString("%6.2f"); } + + /** + * Reports the configuration of this PictureRenderer. + */ + SkString getConfigName() { + SkString config = this->getConfigNameInternal(); + if (!fViewport.isEmpty()) { + config.appendf("_viewport_%ix%i", fViewport.width(), fViewport.height()); + } + if (fScaleFactor != SK_Scalar1) { + config.appendf("_scalar_%f", SkScalarToFloat(fScaleFactor)); + } + if (kRTree_BBoxHierarchyType == fBBoxHierarchyType) { + config.append("_rtree"); + } else if (kQuadTree_BBoxHierarchyType == fBBoxHierarchyType) { + config.append("_quadtree"); + } else if (kTileGrid_BBoxHierarchyType == fBBoxHierarchyType) { + config.append("_grid"); + config.append("_"); + config.appendS32(fGridInfo.fTileInterval.width()); + config.append("x"); + config.appendS32(fGridInfo.fTileInterval.height()); + } +#if SK_SUPPORT_GPU + switch (fDeviceType) { + case kGPU_DeviceType: + if (fSampleCount) { + config.appendf("_msaa%d", fSampleCount); + } else { + config.append("_gpu"); + } + break; + case kNVPR_DeviceType: + config.appendf("_nvprmsaa%d", fSampleCount); + break; +#if SK_ANGLE + case kAngle_DeviceType: + config.append("_angle"); + break; +#endif +#if SK_MESA + case kMesa_DeviceType: + config.append("_mesa"); + break; +#endif + default: + // Assume that no extra info means bitmap. + break; + } +#endif + config.append(fDrawFiltersConfig.c_str()); + return config; + } + +#if SK_SUPPORT_GPU + bool isUsingGpuDevice() { + switch (fDeviceType) { + case kGPU_DeviceType: + case kNVPR_DeviceType: + // fall through +#if SK_ANGLE + case kAngle_DeviceType: + // fall through +#endif +#if SK_MESA + case kMesa_DeviceType: +#endif + return true; + default: + return false; + } + } + + SkGLContextHelper* getGLContext() { + GrContextFactory::GLContextType glContextType + = GrContextFactory::kNull_GLContextType; + switch(fDeviceType) { + case kGPU_DeviceType: + glContextType = GrContextFactory::kNative_GLContextType; + break; + case kNVPR_DeviceType: + glContextType = GrContextFactory::kNVPR_GLContextType; + break; +#if SK_ANGLE + case kAngle_DeviceType: + glContextType = GrContextFactory::kANGLE_GLContextType; + break; +#endif +#if SK_MESA + case kMesa_DeviceType: + glContextType = GrContextFactory::kMESA_GLContextType; + break; +#endif + default: + return NULL; + } + return fGrContextFactory.getGLContext(glContextType); + } + + GrContext* getGrContext() { + return fGrContext; + } +#endif + + SkCanvas* getCanvas() { + return fCanvas; + } + + SkPicture* getPicture() { + return fPicture; + } + + PictureRenderer() + : fJsonSummaryPtr(NULL) + , fDeviceType(kBitmap_DeviceType) + , fEnableWrites(false) + , fBBoxHierarchyType(kNone_BBoxHierarchyType) + , fScaleFactor(SK_Scalar1) +#if SK_SUPPORT_GPU + , fGrContext(NULL) + , fSampleCount(0) +#endif + { + fGridInfo.fMargin.setEmpty(); + fGridInfo.fOffset.setZero(); + fGridInfo.fTileInterval.set(1, 1); + sk_bzero(fDrawFilters, sizeof(fDrawFilters)); + fViewport.set(0, 0); + } + +#if SK_SUPPORT_GPU + virtual ~PictureRenderer() { + SkSafeUnref(fGrContext); + } +#endif + +protected: + SkAutoTUnref<SkCanvas> fCanvas; + SkAutoTUnref<SkPicture> fPicture; + bool fUseChecksumBasedFilenames; + ImageResultsAndExpectations* fJsonSummaryPtr; + SkDeviceTypes fDeviceType; + bool fEnableWrites; + BBoxHierarchyType fBBoxHierarchyType; + DrawFilterFlags fDrawFilters[SkDrawFilter::kTypeCount]; + SkString fDrawFiltersConfig; + SkString fWritePath; + SkString fMismatchPath; + SkString fInputFilename; + SkTileGridFactory::TileGridInfo fGridInfo; // used when fBBoxHierarchyType is TileGrid + + void buildBBoxHierarchy(); + + /** + * Return the total width that should be drawn. If the viewport width has been set greater than + * 0, this will be the minimum of the current SkPicture's width and the viewport's width. + */ + int getViewWidth(); + + /** + * Return the total height that should be drawn. If the viewport height has been set greater + * than 0, this will be the minimum of the current SkPicture's height and the viewport's height. + */ + int getViewHeight(); + + /** + * Scales the provided canvas to the scale factor set by setScaleFactor. + */ + void scaleToScaleFactor(SkCanvas*); + + SkBBHFactory* getFactory(); + uint32_t recordFlags() const { return 0; } + SkCanvas* setupCanvas(); + virtual SkCanvas* setupCanvas(int width, int height); + + /** + * Copy src to dest; if src==NULL, set dest to empty string. + */ + static void CopyString(SkString* dest, const SkString* src); + +private: + SkISize fViewport; + SkScalar fScaleFactor; +#if SK_SUPPORT_GPU + GrContextFactory fGrContextFactory; + GrContext* fGrContext; + int fSampleCount; +#endif + + virtual SkString getConfigNameInternal() = 0; + + typedef SkRefCnt INHERITED; +}; + +/** + * This class does not do any rendering, but its render function executes recording, which we want + * to time. + */ +class RecordPictureRenderer : public PictureRenderer { + virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE; + + virtual SkString getPerIterTimeFormat() SK_OVERRIDE { return SkString("%.4f"); } + + virtual SkString getNormalTimeFormat() SK_OVERRIDE { return SkString("%6.4f"); } + +protected: + virtual SkCanvas* setupCanvas(int width, int height) SK_OVERRIDE; + +private: + virtual SkString getConfigNameInternal() SK_OVERRIDE; +}; + +class PipePictureRenderer : public PictureRenderer { +public: + virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE; + +private: + virtual SkString getConfigNameInternal() SK_OVERRIDE; + + typedef PictureRenderer INHERITED; +}; + +class SimplePictureRenderer : public PictureRenderer { +public: + virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, + const SkString* inputFilename, bool useChecksumBasedFilenames) SK_OVERRIDE; + + virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE; + +private: + virtual SkString getConfigNameInternal() SK_OVERRIDE; + + typedef PictureRenderer INHERITED; +}; + +class TiledPictureRenderer : public PictureRenderer { +public: + TiledPictureRenderer(); + + virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, + const SkString* inputFilename, bool useChecksumBasedFilenames) SK_OVERRIDE; + + /** + * Renders to tiles, rather than a single canvas. + * If fWritePath was provided, a separate file is + * created for each tile, named "path0.png", "path1.png", etc. + * Multithreaded mode currently does not support writing to a file. + */ + virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE; + + virtual void end() SK_OVERRIDE; + + void setTileWidth(int width) { + fTileWidth = width; + } + + int getTileWidth() const { + return fTileWidth; + } + + void setTileHeight(int height) { + fTileHeight = height; + } + + int getTileHeight() const { + return fTileHeight; + } + + void setTileWidthPercentage(double percentage) { + fTileWidthPercentage = percentage; + } + + double getTileWidthPercentage() const { + return fTileWidthPercentage; + } + + void setTileHeightPercentage(double percentage) { + fTileHeightPercentage = percentage; + } + + double getTileHeightPercentage() const { + return fTileHeightPercentage; + } + + void setTileMinPowerOf2Width(int width) { + SkASSERT(SkIsPow2(width) && width > 0); + if (!SkIsPow2(width) || width <= 0) { + return; + } + + fTileMinPowerOf2Width = width; + } + + int getTileMinPowerOf2Width() const { + return fTileMinPowerOf2Width; + } + + virtual TiledPictureRenderer* getTiledRenderer() SK_OVERRIDE { return this; } + + virtual bool supportsTimingIndividualTiles() { return true; } + + /** + * Report the number of tiles in the x and y directions. Must not be called before init. + * @param x Output parameter identifying the number of tiles in the x direction. + * @param y Output parameter identifying the number of tiles in the y direction. + * @return True if the tiles have been set up, and x and y are meaningful. If false, x and y are + * unmodified. + */ + bool tileDimensions(int& x, int&y); + + /** + * Move to the next tile and return its indices. Must be called before calling drawCurrentTile + * for the first time. + * @param i Output parameter identifying the column of the next tile to be drawn on the next + * call to drawNextTile. + * @param j Output parameter identifying the row of the next tile to be drawn on the next call + * to drawNextTile. + * @param True if the tiles have been created and the next tile to be drawn by drawCurrentTile + * is within the range of tiles. If false, i and j are unmodified. + */ + bool nextTile(int& i, int& j); + + /** + * Render one tile. This will draw the same tile each time it is called until nextTile is + * called. The tile rendered will depend on how many calls have been made to nextTile. + * It is an error to call this without first calling nextTile, or if nextTile returns false. + */ + void drawCurrentTile(); + +protected: + SkTDArray<SkRect> fTileRects; + + virtual SkCanvas* setupCanvas(int width, int height) SK_OVERRIDE; + virtual SkString getConfigNameInternal() SK_OVERRIDE; + +private: + int fTileWidth; + int fTileHeight; + double fTileWidthPercentage; + double fTileHeightPercentage; + int fTileMinPowerOf2Width; + + // These variables are only used for timing individual tiles. + // Next tile to draw in fTileRects. + int fCurrentTileOffset; + // Number of tiles in the x direction. + int fTilesX; + // Number of tiles in the y direction. + int fTilesY; + + void setupTiles(); + void setupPowerOf2Tiles(); + + typedef PictureRenderer INHERITED; +}; + +class CloneData; + +class MultiCorePictureRenderer : public TiledPictureRenderer { +public: + explicit MultiCorePictureRenderer(int threadCount); + + ~MultiCorePictureRenderer(); + + virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, + const SkString* inputFilename, bool useChecksumBasedFilenames) SK_OVERRIDE; + + /** + * Behaves like TiledPictureRenderer::render(), only using multiple threads. + */ + virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE; + + virtual void end() SK_OVERRIDE; + + virtual bool supportsTimingIndividualTiles() SK_OVERRIDE { return false; } + +private: + virtual SkString getConfigNameInternal() SK_OVERRIDE; + + const int fNumThreads; + SkTDArray<SkCanvas*> fCanvasPool; + SkThreadPool fThreadPool; + SkPicture* fPictureClones; + CloneData** fCloneData; + SkCountdown fCountdown; + + typedef TiledPictureRenderer INHERITED; +}; + +/** + * This class does not do any rendering, but its render function executes turning an SkPictureRecord + * into an SkPicturePlayback, which we want to time. + */ +class PlaybackCreationRenderer : public PictureRenderer { +public: + virtual void setup() SK_OVERRIDE; + + virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE; + + virtual SkString getPerIterTimeFormat() SK_OVERRIDE { return SkString("%.4f"); } + + virtual SkString getNormalTimeFormat() SK_OVERRIDE { return SkString("%6.4f"); } + +private: + SkAutoTDelete<SkPictureRecorder> fRecorder; + + virtual SkString getConfigNameInternal() SK_OVERRIDE; + + typedef PictureRenderer INHERITED; +}; + +extern PictureRenderer* CreateGatherPixelRefsRenderer(); +extern PictureRenderer* CreatePictureCloneRenderer(); + +} + +#endif // PictureRenderer_DEFINED diff --git a/chromium/third_party/skia/tools/PictureRenderingFlags.cpp b/chromium/third_party/skia/tools/PictureRenderingFlags.cpp new file mode 100644 index 00000000000..5acec267b07 --- /dev/null +++ b/chromium/third_party/skia/tools/PictureRenderingFlags.cpp @@ -0,0 +1,377 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "PictureRenderingFlags.h" + +#include "CopyTilesRenderer.h" +#include "PictureRenderer.h" +#include "picture_utils.h" +#include "SkCommandLineFlags.h" +#include "SkData.h" +#include "SkImage.h" +#include "SkImageDecoder.h" +#include "SkString.h" + +// Alphabetized list of flags used by this file or bench_ and render_pictures. +DEFINE_string(bbh, "none", "bbhType [width height]: Set the bounding box hierarchy type to " + "be used. Accepted values are: none, rtree, quadtree, grid. " + "Not compatible with --pipe. With value " + "'grid', width and height must be specified. 'grid' can " + "only be used with modes tile, record, and " + "playbackCreation."); + + +#if SK_SUPPORT_GPU +#define GPU_CONFIG_STRING "|gpu|msaa4|msaa16|nvprmsaa4|nvprmsaa16" +#else +#define GPU_CONFIG_STRING "" +#endif +#if SK_ANGLE +#define ANGLE_CONFIG_STRING "|angle" +#else +#define ANGLE_CONFIG_STRING "" +#endif +#if SK_MESA +#define MESA_CONFIG_STRING "|mesa" +#else +#define MESA_CONFIG_STRING "" +#endif + +// Although this config does not support all the same options as gm, the names should be kept +// consistent. +DEFINE_string(config, "8888", "[" + "8888" GPU_CONFIG_STRING ANGLE_CONFIG_STRING MESA_CONFIG_STRING + "]: Use the corresponding config."); + +DEFINE_bool(deferImageDecoding, false, "Defer decoding until drawing images. " + "Has no effect if the provided skp does not have its images encoded."); +DEFINE_string(mode, "simple", "Run in the corresponding mode:\n" + "simple: Simple rendering.\n" + "tile width height: Use tiles with the given dimensions or percentages.\n" + "pow2tile minWidth height: Use tiles with widths that are all a power\n" + "\tof two such that they minimize the amount of wasted tile space.\n" + "\tminWidth must be a power of two.\n" + "copyTile width height: Draw the picture, then copy into tiles. If the\n" + "\tpicture is large enough, it is broken into larger tiles to avoid\n" + "\tcreating a large canvas.\n" +// TODO: If bench_pictures and render_pictures were two separate targets, we could use build flags +// to determine which modes to display. + "record: (Only in bench_pictures) Time recording from a picture to a new\n" + "\tpicture.\n" + "playbackCreation: (Only in bench_pictures) Time creation of the \n" + "\tSkPicturePlayback.\n" + "rerecord: (Only in render_pictures) Record the picture as a new skp,\n" + "\twith the bitmaps PNG encoded.\n"); +DEFINE_int32(multi, 1, "Set the number of threads for multi threaded drawing. " + "If > 1, requires tiled rendering."); +DEFINE_bool(pipe, false, "Use SkGPipe rendering. Currently incompatible with \"mode\"."); +DEFINE_string2(readPath, r, "", "skp files or directories of skp files to process."); +DEFINE_double(scale, 1, "Set the scale factor."); +DEFINE_string(tiles, "", "Used with --mode copyTile to specify number of tiles per larger tile " + "in the x and y directions."); +DEFINE_string(viewport, "", "width height: Set the viewport."); + +sk_tools::PictureRenderer* parseRenderer(SkString& error, PictureTool tool) { + error.reset(); + + if (FLAGS_multi <= 0) { + error.printf("--multi must be > 0, was %i", FLAGS_multi); + return NULL; + } + + bool useTiles = false; + const char* widthString = NULL; + const char* heightString = NULL; + bool isPowerOf2Mode = false; + bool isCopyMode = false; + const char* mode = NULL; + bool gridSupported = false; + + SkAutoTUnref<sk_tools::PictureRenderer> renderer; + if (FLAGS_mode.count() >= 1) { + mode = FLAGS_mode[0]; + if (0 == strcmp(mode, "record")) { + renderer.reset(SkNEW(sk_tools::RecordPictureRenderer)); + gridSupported = true; + // undocumented + } else if (0 == strcmp(mode, "clone")) { + renderer.reset(sk_tools::CreatePictureCloneRenderer()); + } else if (0 == strcmp(mode, "tile") || 0 == strcmp(mode, "pow2tile") + || 0 == strcmp(mode, "copyTile")) { + useTiles = true; + + if (0 == strcmp(mode, "pow2tile")) { + isPowerOf2Mode = true; + } else if (0 == strcmp(mode, "copyTile")) { + isCopyMode = true; + } else { + gridSupported = true; + } + + if (FLAGS_mode.count() < 2) { + error.printf("Missing width for --mode %s\n", mode); + return NULL; + } + + widthString = FLAGS_mode[1]; + if (FLAGS_mode.count() < 3) { + error.printf("Missing height for --mode %s\n", mode); + return NULL; + } + + heightString = FLAGS_mode[2]; + } else if (0 == strcmp(mode, "playbackCreation") && kBench_PictureTool == tool) { + renderer.reset(SkNEW(sk_tools::PlaybackCreationRenderer)); + gridSupported = true; + // undocumented + } else if (0 == strcmp(mode, "gatherPixelRefs") && kBench_PictureTool == tool) { + renderer.reset(sk_tools::CreateGatherPixelRefsRenderer()); + } else if (0 == strcmp(mode, "rerecord") && kRender_PictureTool == tool) { + renderer.reset(SkNEW(sk_tools::RecordPictureRenderer)); + // Allow 'mode' to be set to 'simple', but do not create a renderer, so we can + // ensure that pipe does not override a mode besides simple. The renderer will + // be created below. + } else if (0 == strcmp(mode, "simple")) { + gridSupported = true; + } else { + error.printf("%s is not a valid mode for --mode\n", mode); + return NULL; + } + } + + if (useTiles) { + SkASSERT(NULL == renderer); + SkAutoTUnref<sk_tools::TiledPictureRenderer> tiledRenderer; + if (isCopyMode) { + int xTiles = -1; + int yTiles = -1; + if (FLAGS_tiles.count() > 0) { + if (FLAGS_tiles.count() != 2) { + error.printf("--tiles requires an x value and a y value.\n"); + return NULL; + } + xTiles = atoi(FLAGS_tiles[0]); + yTiles = atoi(FLAGS_tiles[1]); + } + + int x, y; + if (xTiles != -1 && yTiles != -1) { + x = xTiles; + y = yTiles; + if (x <= 0 || y <= 0) { + error.printf("--tiles must be given values > 0\n"); + return NULL; + } + } else { + x = y = 4; + } + tiledRenderer.reset(SkNEW_ARGS(sk_tools::CopyTilesRenderer, (x, y))); + } else if (FLAGS_multi > 1) { + tiledRenderer.reset(SkNEW_ARGS(sk_tools::MultiCorePictureRenderer, + (FLAGS_multi))); + } else { + tiledRenderer.reset(SkNEW(sk_tools::TiledPictureRenderer)); + } + + if (isPowerOf2Mode) { + int minWidth = atoi(widthString); + if (!SkIsPow2(minWidth) || minWidth < 0) { + SkString err; + error.printf("-mode %s must be given a width" + " value that is a power of two\n", mode); + return NULL; + } + tiledRenderer->setTileMinPowerOf2Width(minWidth); + } else if (sk_tools::is_percentage(widthString)) { + if (isCopyMode) { + error.printf("--mode %s does not support percentages.\n", mode); + return NULL; + } + tiledRenderer->setTileWidthPercentage(atof(widthString)); + if (!(tiledRenderer->getTileWidthPercentage() > 0)) { + error.printf("--mode %s must be given a width percentage > 0\n", mode); + return NULL; + } + } else { + tiledRenderer->setTileWidth(atoi(widthString)); + if (!(tiledRenderer->getTileWidth() > 0)) { + error.printf("--mode %s must be given a width > 0\n", mode); + return NULL; + } + } + + if (sk_tools::is_percentage(heightString)) { + if (isCopyMode) { + error.printf("--mode %s does not support percentages.\n", mode); + return NULL; + } + tiledRenderer->setTileHeightPercentage(atof(heightString)); + if (!(tiledRenderer->getTileHeightPercentage() > 0)) { + error.printf("--mode %s must be given a height percentage > 0\n", mode); + return NULL; + } + } else { + tiledRenderer->setTileHeight(atoi(heightString)); + if (!(tiledRenderer->getTileHeight() > 0)) { + SkString err; + error.printf("--mode %s must be given a height > 0\n", mode); + return NULL; + } + } + + renderer.reset(tiledRenderer.detach()); + if (FLAGS_pipe) { + error.printf("Pipe rendering is currently not compatible with tiling.\n" + "Turning off pipe.\n"); + } + + } else { // useTiles + if (FLAGS_multi > 1) { + error.printf("Multithreaded drawing requires tiled rendering.\n"); + return NULL; + } + if (FLAGS_pipe) { + if (renderer != NULL) { + error.printf("Pipe is incompatible with other modes.\n"); + return NULL; + } + renderer.reset(SkNEW(sk_tools::PipePictureRenderer)); + } + } + + if (NULL == renderer) { + renderer.reset(SkNEW(sk_tools::SimplePictureRenderer)); + } + + if (FLAGS_viewport.count() > 0) { + if (FLAGS_viewport.count() != 2) { + error.printf("--viewport requires a width and a height.\n"); + return NULL; + } + SkISize viewport; + viewport.fWidth = atoi(FLAGS_viewport[0]); + viewport.fHeight = atoi(FLAGS_viewport[1]); + renderer->setViewport(viewport); + } + + sk_tools::PictureRenderer::SkDeviceTypes deviceType = + sk_tools::PictureRenderer::kBitmap_DeviceType; +#if SK_SUPPORT_GPU + int sampleCount = 0; +#endif + if (FLAGS_config.count() > 0) { + if (0 == strcmp(FLAGS_config[0], "8888")) { + deviceType = sk_tools::PictureRenderer::kBitmap_DeviceType; + } +#if SK_SUPPORT_GPU + else if (0 == strcmp(FLAGS_config[0], "gpu")) { + deviceType = sk_tools::PictureRenderer::kGPU_DeviceType; + if (FLAGS_multi > 1) { + error.printf("GPU not compatible with multithreaded tiling.\n"); + return NULL; + } + } + else if (0 == strcmp(FLAGS_config[0], "msaa4")) { + deviceType = sk_tools::PictureRenderer::kGPU_DeviceType; + if (FLAGS_multi > 1) { + error.printf("GPU not compatible with multithreaded tiling.\n"); + return NULL; + } + sampleCount = 4; + } + else if (0 == strcmp(FLAGS_config[0], "msaa16")) { + deviceType = sk_tools::PictureRenderer::kGPU_DeviceType; + if (FLAGS_multi > 1) { + error.printf("GPU not compatible with multithreaded tiling.\n"); + return NULL; + } + sampleCount = 16; + } + else if (0 == strcmp(FLAGS_config[0], "nvprmsaa4")) { + deviceType = sk_tools::PictureRenderer::kNVPR_DeviceType; + if (FLAGS_multi > 1) { + error.printf("GPU not compatible with multithreaded tiling.\n"); + return NULL; + } + sampleCount = 4; + } + else if (0 == strcmp(FLAGS_config[0], "nvprmsaa16")) { + deviceType = sk_tools::PictureRenderer::kNVPR_DeviceType; + if (FLAGS_multi > 1) { + error.printf("GPU not compatible with multithreaded tiling.\n"); + return NULL; + } + sampleCount = 16; + } +#if SK_ANGLE + else if (0 == strcmp(FLAGS_config[0], "angle")) { + deviceType = sk_tools::PictureRenderer::kAngle_DeviceType; + if (FLAGS_multi > 1) { + error.printf("Angle not compatible with multithreaded tiling.\n"); + return NULL; + } + } +#endif +#if SK_MESA + else if (0 == strcmp(FLAGS_config[0], "mesa")) { + deviceType = sk_tools::PictureRenderer::kMesa_DeviceType; + if (FLAGS_multi > 1) { + error.printf("Mesa not compatible with multithreaded tiling.\n"); + return NULL; + } + } +#endif +#endif + else { + error.printf("%s is not a valid mode for --config\n", FLAGS_config[0]); + return NULL; + } + renderer->setDeviceType(deviceType); +#if SK_SUPPORT_GPU + renderer->setSampleCount(sampleCount); +#endif + } + + + sk_tools::PictureRenderer::BBoxHierarchyType bbhType + = sk_tools::PictureRenderer::kNone_BBoxHierarchyType; + if (FLAGS_bbh.count() > 0) { + const char* type = FLAGS_bbh[0]; + if (0 == strcmp(type, "none")) { + bbhType = sk_tools::PictureRenderer::kNone_BBoxHierarchyType; + } else if (0 == strcmp(type, "quadtree")) { + bbhType = sk_tools::PictureRenderer::kQuadTree_BBoxHierarchyType; + } else if (0 == strcmp(type, "rtree")) { + bbhType = sk_tools::PictureRenderer::kRTree_BBoxHierarchyType; + } else if (0 == strcmp(type, "grid")) { + if (!gridSupported) { + error.printf("'--bbh grid' is not compatible with --mode=%s.\n", mode); + return NULL; + } + bbhType = sk_tools::PictureRenderer::kTileGrid_BBoxHierarchyType; + if (FLAGS_bbh.count() != 3) { + error.printf("--bbh grid requires a width and a height.\n"); + return NULL; + } + int gridWidth = atoi(FLAGS_bbh[1]); + int gridHeight = atoi(FLAGS_bbh[2]); + renderer->setGridSize(gridWidth, gridHeight); + + } else { + error.printf("%s is not a valid value for --bbhType\n", type); + return NULL; + } + if (FLAGS_pipe && sk_tools::PictureRenderer::kNone_BBoxHierarchyType != bbhType) { + error.printf("--pipe and --bbh cannot be used together\n"); + return NULL; + } + } + renderer->setBBoxHierarchyType(bbhType); + renderer->setScaleFactor(SkDoubleToScalar(FLAGS_scale)); + + return renderer.detach(); +} diff --git a/chromium/third_party/skia/tools/PictureRenderingFlags.h b/chromium/third_party/skia/tools/PictureRenderingFlags.h new file mode 100644 index 00000000000..29215ac7e12 --- /dev/null +++ b/chromium/third_party/skia/tools/PictureRenderingFlags.h @@ -0,0 +1,33 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef PICTURE_RENDERING_FLAGS +#define PICTURE_RENDERING_FLAGS + +class SkString; + +namespace sk_tools { + class PictureRenderer; +} + +enum PictureTool { + kBench_PictureTool, + kRender_PictureTool, +}; + +/** + * Uses SkCommandLineFlags to parse the command line, and returns a PictureRenderer + * reflecting the flags used. Assumes that SkCommandLineFlags::Parse has + * been called. + * @param error If there is an error or warning, it will be stored in error. + * @param tool Which tool is being used. + * @return PictureRenderer A PictureRenderer with the settings specified + * on the command line, or NULL if the command line is invalid. + */ +sk_tools::PictureRenderer* parseRenderer(SkString& error, PictureTool tool); + +#endif // PICTURE_RENDERING_FLAGS diff --git a/chromium/third_party/skia/tools/PictureResultsWriter.h b/chromium/third_party/skia/tools/PictureResultsWriter.h new file mode 100644 index 00000000000..d4a15765f03 --- /dev/null +++ b/chromium/third_party/skia/tools/PictureResultsWriter.h @@ -0,0 +1,232 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Classes for writing out bench results in various formats. + */ + +#ifndef SkPictureResultsWriter_DEFINED +#define SkPictureResultsWriter_DEFINED + +#include "BenchLogger.h" +#include "ResultsWriter.h" +#include "SkJSONCPP.h" +#include "SkStream.h" +#include "SkString.h" +#include "SkTArray.h" +#include "TimerData.h" + +/** + * Base class for writing picture bench results. + */ +class PictureResultsWriter : SkNoncopyable { +public: + enum TileFlags {kPurging, kAvg}; + + PictureResultsWriter() {} + virtual ~PictureResultsWriter() {} + + virtual void bench(const char name[], int32_t x, int32_t y) = 0; + virtual void tileConfig(SkString configName) = 0; + virtual void tileMeta(int x, int y, int tx, int ty) = 0; + virtual void addTileFlag(PictureResultsWriter::TileFlags flag) = 0; + virtual void tileData( + TimerData* data, + const char format[], + const TimerData::Result result, + uint32_t timerTypes, + int numInnerLoops = 1) = 0; + virtual void end() = 0; +}; + +/** + * This class allows bench data to be piped into multiple + * PictureResultWriter classes. It does not own any classes + * passed to it, so the owner is required to manage any classes + * passed to PictureResultsMultiWriter */ +class PictureResultsMultiWriter : public PictureResultsWriter { +public: + PictureResultsMultiWriter() + : fWriters() {} + void add(PictureResultsWriter* newWriter) { + fWriters.push_back(newWriter); + } + virtual ~PictureResultsMultiWriter() {} + virtual void bench(const char name[], int32_t x, int32_t y) { + for(int i=0; i<fWriters.count(); ++i) { + fWriters[i]->bench(name, x, y); + } + } + virtual void tileConfig(SkString configName) { + for(int i=0; i<fWriters.count(); ++i) { + fWriters[i]->tileConfig(configName); + } + } + virtual void tileMeta(int x, int y, int tx, int ty) { + for(int i=0; i<fWriters.count(); ++i) { + fWriters[i]->tileMeta(x, y, tx, ty); + } + } + virtual void addTileFlag(PictureResultsWriter::TileFlags flag) { + for(int i=0; i<fWriters.count(); ++i) { + fWriters[i]->addTileFlag(flag); + } + } + virtual void tileData( + TimerData* data, + const char format[], + const TimerData::Result result, + uint32_t timerTypes, + int numInnerLoops = 1) { + for(int i=0; i<fWriters.count(); ++i) { + fWriters[i]->tileData(data, format, result, timerTypes, + numInnerLoops); + } + } + virtual void end() { + for(int i=0; i<fWriters.count(); ++i) { + fWriters[i]->end(); + } + } +private: + SkTArray<PictureResultsWriter*> fWriters; +}; + +/** + * Writes to BenchLogger to mimic original behavior + */ +class PictureResultsLoggerWriter : public PictureResultsWriter { +private: + void logProgress(const char str[]) { + if(fLogger != NULL) { + fLogger->logProgress(str); + } + } +public: + PictureResultsLoggerWriter(BenchLogger* log) + : fLogger(log), currentLine() {} + virtual void bench(const char name[], int32_t x, int32_t y) { + SkString result; + result.printf("running bench [%i %i] %s ", x, y, name); + this->logProgress(result.c_str()); + } + virtual void tileConfig(SkString configName) { + currentLine = configName; + } + virtual void tileMeta(int x, int y, int tx, int ty) { + currentLine.appendf(": tile [%i,%i] out of [%i,%i]", x, y, tx, ty); + } + virtual void addTileFlag(PictureResultsWriter::TileFlags flag) { + if(flag == PictureResultsWriter::kPurging) { + currentLine.append(" <withPurging>"); + } else if(flag == PictureResultsWriter::kAvg) { + currentLine.append(" <averaged>"); + } + } + virtual void tileData( + TimerData* data, + const char format[], + const TimerData::Result result, + uint32_t timerTypes, + int numInnerLoops = 1) { + SkString results = data->getResult(format, result, + currentLine.c_str(), timerTypes, numInnerLoops); + results.append("\n"); + this->logProgress(results.c_str()); + } + virtual void end() {} +private: + BenchLogger* fLogger; + SkString currentLine; +}; + +/** + * This PictureResultsWriter collects data in a JSON node + * + * The format is something like + * { + * benches: [ + * { + * name: "Name_of_test" + * tilesets: [ + * { + * name: "Name of the configuration" + * tiles: [ + * { + * flags: { + * purging: true //Flags for the current tile + * // are put here + * } + * data: { + * wsecs: [....] //Actual data ends up here + * } + * } + * ] + * } + * ] + * } + * ] + * }*/ + +class PictureJSONResultsWriter : public PictureResultsWriter { +public: + PictureJSONResultsWriter(const char filename[]) + : fFilename(filename), + fRoot(), + fCurrentBench(NULL), + fCurrentTileSet(NULL), + fCurrentTile(NULL) {} + + virtual void bench(const char name[], int32_t x, int32_t y) { + SkString sk_name(name); + sk_name.append("_"); + sk_name.appendS32(x); + sk_name.append("_"); + sk_name.appendS32(y); + Json::Value* bench_node = SkFindNamedNode(&fRoot["benches"], sk_name.c_str()); + fCurrentBench = &(*bench_node)["tileSets"]; + } + virtual void tileConfig(SkString configName) { + SkASSERT(fCurrentBench != NULL); + fCurrentTileSet = SkFindNamedNode(fCurrentBench, configName.c_str()); + fCurrentTile = &(*fCurrentTileSet)["tiles"][0]; + } + virtual void tileMeta(int x, int y, int tx, int ty) { + SkASSERT(fCurrentTileSet != NULL); + (*fCurrentTileSet)["tx"] = tx; + (*fCurrentTileSet)["ty"] = ty; + fCurrentTile = &(*fCurrentTileSet)["tiles"][x+tx*y]; + } + virtual void addTileFlag(PictureResultsWriter::TileFlags flag) { + SkASSERT(fCurrentTile != NULL); + if(flag == PictureResultsWriter::kPurging) { + (*fCurrentTile)["flags"]["purging"] = true; + } else if(flag == PictureResultsWriter::kAvg) { + (*fCurrentTile)["flags"]["averaged"] = true; + } + } + virtual void tileData( + TimerData* data, + const char format[], + const TimerData::Result result, + uint32_t timerTypes, + int numInnerLoops = 1) { + SkASSERT(fCurrentTile != NULL); + (*fCurrentTile)["data"] = data->getJSON(timerTypes, result, numInnerLoops); + } + virtual void end() { + SkFILEWStream stream(fFilename.c_str()); + stream.writeText(Json::FastWriter().write(fRoot).c_str()); + stream.flush(); + } +private: + SkString fFilename; + Json::Value fRoot; + Json::Value *fCurrentBench; + Json::Value *fCurrentTileSet; + Json::Value *fCurrentTile; +}; + +#endif diff --git a/chromium/third_party/skia/tools/Resources.cpp b/chromium/third_party/skia/tools/Resources.cpp new file mode 100644 index 00000000000..756d14ad3ac --- /dev/null +++ b/chromium/third_party/skia/tools/Resources.cpp @@ -0,0 +1,17 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "Resources.h" + +#include "SkCommandLineFlags.h" +#include "SkOSFile.h" + +DEFINE_string2(resourcePath, i, "resources", "Directory with test resources: images, fonts, etc."); + +SkString GetResourcePath(const char* resource) { + return SkOSPath::SkPathJoin(FLAGS_resourcePath[0], resource); +} diff --git a/chromium/third_party/skia/tools/Resources.h b/chromium/third_party/skia/tools/Resources.h new file mode 100644 index 00000000000..a10612b78d9 --- /dev/null +++ b/chromium/third_party/skia/tools/Resources.h @@ -0,0 +1,15 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Resources_DEFINED +#define Resources_DEFINED + +#include "SkString.h" + +SkString GetResourcePath(const char* resource = ""); + +#endif // Resources_DEFINED diff --git a/chromium/third_party/skia/tools/Stats.h b/chromium/third_party/skia/tools/Stats.h new file mode 100644 index 00000000000..2370084fd65 --- /dev/null +++ b/chromium/third_party/skia/tools/Stats.h @@ -0,0 +1,32 @@ +#ifndef Stats_DEFINED +#define Stats_DEFINED + +struct Stats { + Stats(const double samples[], int n) { + min = samples[0]; + max = samples[0]; + for (int i = 0; i < n; i++) { + if (samples[i] < min) { min = samples[i]; } + if (samples[i] > max) { max = samples[i]; } + } + + double sum = 0.0; + for (int i = 0 ; i < n; i++) { + sum += samples[i]; + } + mean = sum / n; + + double err = 0.0; + for (int i = 0 ; i < n; i++) { + err += (samples[i] - mean) * (samples[i] - mean); + } + var = err / (n-1); + } + + double min; + double max; + double mean; // Estimate of population mean. + double var; // Estimate of population variance. +}; + +#endif//Stats_DEFINED diff --git a/chromium/third_party/skia/tools/__init__.py b/chromium/third_party/skia/tools/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/chromium/third_party/skia/tools/__init__.py diff --git a/chromium/third_party/skia/tools/add_codereview_message.py b/chromium/third_party/skia/tools/add_codereview_message.py new file mode 100755 index 00000000000..6710390ac1b --- /dev/null +++ b/chromium/third_party/skia/tools/add_codereview_message.py @@ -0,0 +1,148 @@ +#!/usr/bin/python2 + +# Copyright 2014 Google Inc. +# +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Add message to codereview issue. + +This script takes a codereview URL or a codereview issue number as its +argument and a (possibly multi-line) message on stdin. It then calls +`git cl upload` to append the message to the given codereview issue. + +Usage: + echo MESSAGE | %prog -c CHECKOUT_PATH CODEREVIEW_ISSUE +or: + cd /path/to/git/checkout + %prog CODEREVIEW_ISSUE <<EOF + MESSAGE + EOF +or: + %prog --help +""" + +import optparse +import os +import sys + +import git_utils +import misc_utils + + +DEFAULT_REVIEWERS = ','.join([ + 'rmistry@google.com', + 'reed@google.com', + 'bsalomon@google.com', + 'robertphillips@google.com', + ]) + + +DEFAULT_CC_LIST = ','.join([ + 'skia-team@google.com', + ]) + + +def add_codereview_message(codereview_url, message, checkout_path, + skip_cl_upload, verbose, reviewers, cclist): + """Add a message to a given codereview. + + Args: + codereview_url: (string) we will extract the issue number from + this url, or this could simply be the issue number. + message: (string) will be passed to `git cl upload -m $MESSAGE` + checkout_path: (string) location of the git + repository checkout to be used. + skip_cl_upload: (boolean) if true, don't actually + add the message and keep the temporary branch around. + verbose: (boolean) print out details useful for debugging. + reviewers: (string) comma-separated list of reviewers + cclist: (string) comma-separated list of addresses to be + carbon-copied + """ + # pylint: disable=I0011,R0913 + git = git_utils.git_executable() + issue = codereview_url.strip('/').split('/')[-1] + vsp = misc_utils.VerboseSubprocess(verbose) + if skip_cl_upload: + branch_name = 'issue_%s' % issue + else: + branch_name = None + upstream = 'origin/master' + + with misc_utils.ChangeDir(checkout_path, verbose): + vsp.check_call([git, 'fetch', '-q', 'origin']) + + with git_utils.ChangeGitBranch(branch_name, upstream, verbose): + vsp.check_call([git, 'cl', 'patch', issue]) + + git_upload = [ + git, 'cl', 'upload', '-t', 'bot report', '-m', message] + if cclist: + git_upload.append('--cc=' + cclist) + if reviewers: + git_upload.append('--reviewers=' + reviewers) + + if skip_cl_upload: + branch_name = git_utils.git_branch_name(verbose) + space = ' ' + print 'You should call:' + misc_utils.print_subprocess_args(space, ['cd', os.getcwd()]) + misc_utils.print_subprocess_args( + space, [git, 'checkout', branch_name]) + misc_utils.print_subprocess_args(space, git_upload) + else: + vsp.check_call(git_upload) + print vsp.check_output([git, 'cl', 'issue']) + + +def main(argv): + """main function; see module-level docstring and GetOptionParser help. + + Args: + argv: sys.argv[1:]-type argument list. + """ + option_parser = optparse.OptionParser(usage=__doc__) + option_parser.add_option( + '-c', '--checkout_path', + default=os.curdir, + help='Path to the Git repository checkout,' + ' defaults to current working directory.') + option_parser.add_option( + '', '--skip_cl_upload', action='store_true', default=False, + help='Skip the cl upload step; useful for testing.') + option_parser.add_option( + '', '--verbose', action='store_true', dest='verbose', default=False, + help='Do not suppress the output from `git cl`.',) + option_parser.add_option( + '', '--git_path', default='git', + help='Git executable, defaults to "git".',) + option_parser.add_option( + '', '--reviewers', default=DEFAULT_REVIEWERS, + help=('Comma-separated list of reviewers. Default is "%s".' + % DEFAULT_REVIEWERS)) + option_parser.add_option( + '', '--cc', default=DEFAULT_CC_LIST, + help=('Comma-separated list of addresses to be carbon-copied.' + ' Default is "%s".' % DEFAULT_CC_LIST)) + + options, arguments = option_parser.parse_args(argv) + + if not options.checkout_path: + option_parser.error('Must specify checkout_path.') + if not git_utils.git_executable(): + option_parser.error('Invalid git executable.') + if len(arguments) > 1: + option_parser.error('Extra arguments.') + if len(arguments) != 1: + option_parser.error('Missing Codereview URL.') + + message = sys.stdin.read() + add_codereview_message(arguments[0], message, options.checkout_path, + options.skip_cl_upload, options.verbose, + options.reviewers, options.cc) + + +if __name__ == '__main__': + main(sys.argv[1:]) + diff --git a/chromium/third_party/skia/tools/bbh_shootout.cpp b/chromium/third_party/skia/tools/bbh_shootout.cpp new file mode 100644 index 00000000000..e657917aad9 --- /dev/null +++ b/chromium/third_party/skia/tools/bbh_shootout.cpp @@ -0,0 +1,194 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "BenchTimer.h" +#include "Benchmark.h" +#include "LazyDecodeBitmap.h" +#include "PictureBenchmark.h" +#include "PictureRenderer.h" +#include "SkCommandLineFlags.h" +#include "SkForceLinking.h" +#include "SkGraphics.h" +#include "SkStream.h" +#include "SkString.h" +#include "SkTArray.h" + +typedef sk_tools::PictureRenderer::BBoxHierarchyType BBoxType; +static const int kBBoxTypeCount = sk_tools::PictureRenderer::kLast_BBoxHierarchyType + 1; + + +DEFINE_string2(skps, r, "", "The list of SKPs to benchmark."); +DEFINE_string(bb_types, "", "The set of bbox types to test. If empty, all are tested. " + "Should be one or more of none, quadtree, rtree, tilegrid."); +DEFINE_int32(record, 100, "Number of times to record each SKP."); +DEFINE_int32(playback, 1, "Number of times to playback each SKP."); +DEFINE_int32(tilesize, 256, "The size of a tile."); + +struct Measurement { + SkString fName; + double fRecordAverage[kBBoxTypeCount]; + double fPlaybackAverage[kBBoxTypeCount]; +}; + +const char* kBBoxHierarchyTypeNames[kBBoxTypeCount] = { + "none", // kNone_BBoxHierarchyType + "quadtree", // kQuadTree_BBoxHierarchyType + "rtree", // kRTree_BBoxHierarchyType + "tilegrid", // kTileGrid_BBoxHierarchyType +}; + +static SkPicture* pic_from_path(const char path[]) { + SkFILEStream stream(path); + if (!stream.isValid()) { + SkDebugf("-- Can't open '%s'\n", path); + return NULL; + } + return SkPicture::CreateFromStream(&stream, &sk_tools::LazyDecodeBitmap); +} + +/** + * This function is the sink to which all work ends up going. + * @param renderer The renderer to use to perform the work. + * To measure rendering, use a TiledPictureRenderer. + * To measure recording, use a RecordPictureRenderer. + * @param bBoxType The bounding box hierarchy type to use. + * @param pic The picture to draw to the renderer. + * @param numRepeats The number of times to repeat the draw. + * @param timer The timer used to benchmark the work. + */ +static void do_benchmark_work(sk_tools::PictureRenderer* renderer, + BBoxType bBoxType, + SkPicture* pic, + const int numRepeats, + BenchTimer* timer) { + renderer->setBBoxHierarchyType(bBoxType); + renderer->setGridSize(FLAGS_tilesize, FLAGS_tilesize); + renderer->init(pic, NULL, NULL, NULL, false); + + SkDebugf("%s %d times...\n", renderer->getConfigName().c_str(), numRepeats); + for (int i = 0; i < numRepeats; ++i) { + renderer->setup(); + // Render once to fill caches + renderer->render(); + // Render again to measure + timer->start(); + renderer->render(); + timer->end(); + } +} + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + SkCommandLineFlags::Parse(argc, argv); + SkAutoGraphics ag; + bool includeBBoxType[kBBoxTypeCount]; + for (int bBoxType = 0; bBoxType < kBBoxTypeCount; ++bBoxType) { + includeBBoxType[bBoxType] = (FLAGS_bb_types.count() == 0) || + FLAGS_bb_types.contains(kBBoxHierarchyTypeNames[bBoxType]); + } + // go through all the pictures + SkTArray<Measurement> measurements; + for (int index = 0; index < FLAGS_skps.count(); ++index) { + const char* path = FLAGS_skps[index]; + SkPicture* picture = pic_from_path(path); + if (NULL == picture) { + SkDebugf("Couldn't create picture. Ignoring path: %s\n", path); + continue; + } + SkDebugf("Benchmarking path: %s\n", path); + Measurement& measurement = measurements.push_back(); + measurement.fName = path; + for (int bBoxType = 0; bBoxType < kBBoxTypeCount; ++bBoxType) { + if (!includeBBoxType[bBoxType]) { continue; } + if (FLAGS_playback > 0) { + sk_tools::TiledPictureRenderer playbackRenderer; + BenchTimer playbackTimer; + do_benchmark_work(&playbackRenderer, (BBoxType)bBoxType, + picture, FLAGS_playback, &playbackTimer); + measurement.fPlaybackAverage[bBoxType] = playbackTimer.fCpu; + } + if (FLAGS_record > 0) { + sk_tools::RecordPictureRenderer recordRenderer; + BenchTimer recordTimer; + do_benchmark_work(&recordRenderer, (BBoxType)bBoxType, + picture, FLAGS_record, &recordTimer); + measurement.fRecordAverage[bBoxType] = recordTimer.fCpu; + } + } + } + + Measurement globalMeasurement; + for (int bBoxType = 0; bBoxType < kBBoxTypeCount; ++bBoxType) { + if (!includeBBoxType[bBoxType]) { continue; } + globalMeasurement.fPlaybackAverage[bBoxType] = 0; + globalMeasurement.fRecordAverage[bBoxType] = 0; + for (int index = 0; index < measurements.count(); ++index) { + const Measurement& measurement = measurements[index]; + globalMeasurement.fPlaybackAverage[bBoxType] += + measurement.fPlaybackAverage[bBoxType]; + globalMeasurement.fRecordAverage[bBoxType] += + measurement.fRecordAverage[bBoxType]; + } + globalMeasurement.fPlaybackAverage[bBoxType] /= measurements.count(); + globalMeasurement.fRecordAverage[bBoxType] /= measurements.count(); + } + + // Output gnuplot readable histogram data.. + const char* pbTitle = "bbh_shootout_playback.dat"; + const char* recTitle = "bbh_shootout_record.dat"; + SkFILEWStream playbackOut(pbTitle); + SkFILEWStream recordOut(recTitle); + recordOut.writeText("# "); + playbackOut.writeText("# "); + SkDebugf("---\n"); + for (int bBoxType = 0; bBoxType < kBBoxTypeCount; ++bBoxType) { + if (!includeBBoxType[bBoxType]) { continue; } + SkString out; + out.printf("%s ", kBBoxHierarchyTypeNames[bBoxType]); + recordOut.writeText(out.c_str()); + playbackOut.writeText(out.c_str()); + + if (FLAGS_record > 0) { + SkDebugf("Average %s recording time: %.3fms\n", + kBBoxHierarchyTypeNames[bBoxType], + globalMeasurement.fRecordAverage[bBoxType]); + } + if (FLAGS_playback > 0) { + SkDebugf("Average %s playback time: %.3fms\n", + kBBoxHierarchyTypeNames[bBoxType], + globalMeasurement.fPlaybackAverage[bBoxType]); + } + } + recordOut.writeText("\n"); + playbackOut.writeText("\n"); + // Write to file, and save recording averages. + for (int index = 0; index < measurements.count(); ++index) { + const Measurement& measurement = measurements[index]; + SkString pbLine; + SkString recLine; + + pbLine.printf("%d", index); + recLine.printf("%d", index); + for (int bBoxType = 0; bBoxType < kBBoxTypeCount; ++bBoxType) { + if (!includeBBoxType[bBoxType]) { continue; } + pbLine.appendf(" %f", measurement.fPlaybackAverage[bBoxType]); + recLine.appendf(" %f", measurement.fRecordAverage[bBoxType]); + } + pbLine.appendf("\n"); + recLine.appendf("\n"); + playbackOut.writeText(pbLine.c_str()); + recordOut.writeText(recLine.c_str()); + } + SkDebugf("\nWrote data to gnuplot-readable files: %s %s\n", pbTitle, recTitle); + return 0; +} + +#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL) +int main(int argc, char** argv) { + return tool_main(argc, argv); +} +#endif diff --git a/chromium/third_party/skia/tools/bench_pictures.cfg b/chromium/third_party/skia/tools/bench_pictures.cfg new file mode 100644 index 00000000000..ab049e3163e --- /dev/null +++ b/chromium/third_party/skia/tools/bench_pictures.cfg @@ -0,0 +1,83 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +""" +This file defines the configurations in which bench_pictures should be run +on various platforms. The buildbots read these configurations from the +bench_pictures_cfg dictionary. Everything else in this file exists to help in +constructing that dictionary. + +This code is executed directly on the buildbot so that convenient things like +variables and loops can be used to avoid unnecessary verbosity. With great power +comes great responsibility; don't put any nasty code here. To reiterate, code in +this file will be directly executed on the build slaves. +""" + + +import os +import sys + + +if 'import_path' in globals(): + sys.path.append(import_path) + + +from bench_pictures_cfg_helper import * + + +# Default tile sizes +DEFAULT_TILE_X = '256' +DEFAULT_TILE_Y = '256' + +# Default viewport size +DEFAULT_VIEWPORT_X = 1000 +DEFAULT_VIEWPORT_Y = 1000 + +# Default scale factor for scaled configs. +DEFAULT_SCALE = 1.1 + +# Configs to run on most bots +default_configs = [ + # Viewport CPU and GPU + ViewportBitmapConfig(DEFAULT_VIEWPORT_X, DEFAULT_VIEWPORT_Y), + ViewportGPUConfig(DEFAULT_VIEWPORT_X, DEFAULT_VIEWPORT_Y), + + # Scaled viewport, CPU and GPU + ViewportBitmapConfig(DEFAULT_VIEWPORT_X, DEFAULT_VIEWPORT_Y, + scale=str(DEFAULT_SCALE)), + ViewportGPUConfig(DEFAULT_VIEWPORT_X, DEFAULT_VIEWPORT_Y, + scale=str(DEFAULT_SCALE)), +] + +default_no_gpu = [cfg for cfg in default_configs if cfg['config'] != 'gpu'] + + +msaa4 = Config(config='msaa4', viewport=[str(DEFAULT_VIEWPORT_X), + str(DEFAULT_VIEWPORT_Y)]) + +msaa16 = Config(config='msaa16', viewport=[str(DEFAULT_VIEWPORT_X), + str(DEFAULT_VIEWPORT_Y)]) + +viewport_angle = Config(config='angle', viewport=[str(DEFAULT_VIEWPORT_X), + str(DEFAULT_VIEWPORT_Y)]) + +# This dictionary defines the sets of configs for all platforms. Each config is +# a dictionary of key/value pairs directly corresponding to the command-line +# flags passed to bench_pictures. +bench_pictures_cfg = { + 'angle': [viewport_angle], + 'debug': [ViewportBitmapConfig(DEFAULT_VIEWPORT_X, DEFAULT_VIEWPORT_Y)], + 'default': default_configs, + 'no_gpu': default_no_gpu, + 'nexus_s': default_no_gpu, + 'xoom': default_configs, + 'galaxy_nexus': default_configs, + 'nexus_4': default_configs + [msaa4], + 'nexus_7': default_configs, + 'nexus_10': default_configs + [msaa4], + 'razr_i': default_configs + [msaa4], + 'intel_rhb': default_no_gpu, + 'default_msaa16': default_configs + [msaa16], +} diff --git a/chromium/third_party/skia/tools/bench_pictures_cfg_helper.py b/chromium/third_party/skia/tools/bench_pictures_cfg_helper.py new file mode 100644 index 00000000000..2670d775483 --- /dev/null +++ b/chromium/third_party/skia/tools/bench_pictures_cfg_helper.py @@ -0,0 +1,112 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +""" Helper functions to be used in bench_pictures.cfg. """ + + +def Config(**kwargs): + config = {} + for key in kwargs: + config[key] = kwargs[key] + return config + + +def TileArgs(tile_x, tile_y, timeIndividualTiles=True): + config = {'mode': ['tile', str(tile_x), str(tile_y)]} + if timeIndividualTiles: + config['timeIndividualTiles'] = True + return config + + +def BitmapConfig(**kwargs): + return Config(config='8888', **kwargs) + + +def GPUConfig(**kwargs): + return Config(config='gpu', **kwargs) + + +def TiledBitmapConfig(tile_x, tile_y, timeIndividualTiles=True, **kwargs): + return BitmapConfig(**dict(TileArgs(tile_x, tile_y, + timeIndividualTiles=timeIndividualTiles).items() + kwargs.items())) + + +def TiledGPUConfig(tile_x, tile_y, **kwargs): + return GPUConfig(**dict(TileArgs(tile_x, tile_y).items() + kwargs.items())) + + +def TiledConfig(tile_x, tile_y, timeIndividualTiles=True, **kwargs): + return Config(**dict(TileArgs(tile_x, tile_y, + timeIndividualTiles=timeIndividualTiles).items() + kwargs.items())) + + +def ViewportBitmapConfig(viewport_x, viewport_y, **kwargs): + return BitmapConfig(viewport=[str(viewport_x), str(viewport_y)], **kwargs) + + +def ViewportGPUConfig(viewport_x, viewport_y, **kwargs): + return GPUConfig(viewport=[str(viewport_x), str(viewport_y)], **kwargs) + + +def ViewportRTreeConfig(viewport_x, viewport_y, **kwargs): + return RTreeConfig(mode='simple', viewport=[str(viewport_x), str(viewport_y)], + **kwargs) + + +def ViewportGridConfig(viewport_x, viewport_y, **kwargs): + return GridConfig(viewport_x, viewport_y, mode='simple', + viewport=[str(viewport_x), str(viewport_y)], **kwargs) + + +def CopyTilesConfig(tile_x, tile_y, **kwargs): + return BitmapConfig(mode=['copyTile', str(tile_x), str(tile_y)], **kwargs) + + +def RecordConfig(**kwargs): + return BitmapConfig(mode='record', **kwargs) + + +def PlaybackCreationConfig(**kwargs): + return BitmapConfig(mode='playbackCreation', **kwargs) + + +def MultiThreadTileConfig(threads, tile_x, tile_y, **kwargs): + return TiledBitmapConfig(tile_x=tile_x, tile_y=tile_y, + timeIndividualTiles=False, multi=str(threads), + **kwargs) + + +def RTreeConfig(**kwargs): + return BitmapConfig(bbh='rtree', **kwargs) + + +def GridConfig(tile_x, tile_y, mode, **kwargs): + return BitmapConfig(mode=mode, bbh=['grid', str(tile_x), str(tile_y)], + **kwargs) + + +def RecordRTreeConfig(**kwargs): + return RTreeConfig(mode='record', **kwargs) + + +def PlaybackCreationRTreeConfig(**kwargs): + return RTreeConfig(mode='playbackCreation', **kwargs) + + +def TileRTreeConfig(tile_x, tile_y, **kwargs): + return RTreeConfig(**dict(TileArgs(tile_x, tile_y).items() + kwargs.items())) + + +def RecordGridConfig(tile_x, tile_y, **kwargs): + return GridConfig(tile_x=tile_x, tile_y=tile_y, mode='record', **kwargs) + + +def PlaybackCreationGridConfig(tile_x, tile_y, **kwargs): + return GridConfig(tile_x, tile_y, mode='playbackCreation') + + +def TileGridConfig(tile_x, tile_y, **kwargs): + return GridConfig(tile_x, tile_y, + **dict(TileArgs(tile_x, tile_y).items() + kwargs.items())) diff --git a/chromium/third_party/skia/tools/bench_pictures_main.cpp b/chromium/third_party/skia/tools/bench_pictures_main.cpp new file mode 100644 index 00000000000..d9b767b2aa5 --- /dev/null +++ b/chromium/third_party/skia/tools/bench_pictures_main.cpp @@ -0,0 +1,465 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "BenchLogger.h" +#include "BenchTimer.h" +#include "CopyTilesRenderer.h" +#include "CrashHandler.h" +#include "LazyDecodeBitmap.h" +#include "PictureBenchmark.h" +#include "PictureRenderingFlags.h" +#include "PictureResultsWriter.h" +#include "SkCommandLineFlags.h" +#include "SkData.h" +#include "SkDiscardableMemoryPool.h" +#include "SkGraphics.h" +#include "SkImageDecoder.h" +#include "SkMath.h" +#include "SkOSFile.h" +#include "SkPicture.h" +#include "SkStream.h" +#include "picture_utils.h" + +BenchLogger gLogger; +PictureResultsLoggerWriter gLogWriter(&gLogger); +PictureResultsMultiWriter gWriter; + +// Flags used by this file, in alphabetical order. +DEFINE_bool(countRAM, false, "Count the RAM used for bitmap pixels in each skp file"); +DECLARE_bool(deferImageDecoding); +DEFINE_string(filter, "", + "type:flag : Enable canvas filtering to disable a paint flag, " + "use no blur or low quality blur, or use no hinting or " + "slight hinting. For all flags except AAClip, specify the " + "type of primitive to effect, or choose all. for AAClip " + "alone, the filter affects all clips independent of type. " + "Specific flags are listed above."); +DEFINE_string(logFile, "", "Destination for writing log output, in addition to stdout."); +DEFINE_bool(logPerIter, false, "Log each repeat timer instead of mean."); +DEFINE_string(jsonLog, "", "Destination for writing JSON data."); +DEFINE_bool(min, false, "Print the minimum times (instead of average)."); +DECLARE_int32(multi); +DECLARE_string(readPath); +DEFINE_int32(repeat, 1, "Set the number of times to repeat each test."); +DEFINE_bool(timeIndividualTiles, false, "Report times for drawing individual tiles, rather than " + "times for drawing the whole page. Requires tiled rendering."); +DEFINE_bool(purgeDecodedTex, false, "Purge decoded and GPU-uploaded textures " + "after each iteration."); +DEFINE_string(timers, "c", "[wcgWC]*: Display wall, cpu, gpu, truncated wall or truncated cpu time" + " for each picture."); +DEFINE_bool(trackDeferredCaching, false, "Only meaningful with --deferImageDecoding and " + "SK_LAZY_CACHE_STATS set to true. Report percentage of cache hits when using " + "deferred image decoding."); + +DEFINE_bool(preprocess, false, "If true, perform device specific preprocessing before timing."); + +static char const * const gFilterTypes[] = { + "paint", + "point", + "line", + "bitmap", + "rect", + "oval", + "path", + "text", + "all", +}; + +static const size_t kFilterTypesCount = sizeof(gFilterTypes) / sizeof(gFilterTypes[0]); + +static char const * const gFilterFlags[] = { + "antiAlias", + "filterBitmap", + "dither", + "underlineText", + "strikeThruText", + "fakeBoldText", + "linearText", + "subpixelText", + "devKernText", + "LCDRenderText", + "embeddedBitmapText", + "autoHinting", + "verticalText", + "genA8FromLCD", + "blur", + "hinting", + "slightHinting", + "AAClip", +}; + +static const size_t kFilterFlagsCount = sizeof(gFilterFlags) / sizeof(gFilterFlags[0]); + +static SkString filtersName(sk_tools::PictureRenderer::DrawFilterFlags* drawFilters) { + int all = drawFilters[0]; + size_t tIndex; + for (tIndex = 1; tIndex < SkDrawFilter::kTypeCount; ++tIndex) { + all &= drawFilters[tIndex]; + } + SkString result; + for (size_t fIndex = 0; fIndex < kFilterFlagsCount; ++fIndex) { + SkString types; + if (all & (1 << fIndex)) { + types = gFilterTypes[SkDrawFilter::kTypeCount]; + } else { + for (tIndex = 0; tIndex < SkDrawFilter::kTypeCount; ++tIndex) { + if (drawFilters[tIndex] & (1 << fIndex)) { + types += gFilterTypes[tIndex]; + } + } + } + if (!types.size()) { + continue; + } + result += "_"; + result += types; + result += "."; + result += gFilterFlags[fIndex]; + } + return result; +} + +static SkString filterTypesUsage() { + SkString result; + for (size_t index = 0; index < kFilterTypesCount; ++index) { + result += gFilterTypes[index]; + if (index < kFilterTypesCount - 1) { + result += " | "; + } + } + return result; +} + +static SkString filterFlagsUsage() { + SkString result; + size_t len = 0; + for (size_t index = 0; index < kFilterFlagsCount; ++index) { + result += gFilterFlags[index]; + if (result.size() - len >= 72) { + result += "\n\t\t"; + len = result.size(); + } + if (index < kFilterFlagsCount - 1) { + result += " | "; + } + } + return result; +} + +#if SK_LAZY_CACHE_STATS +static int32_t gTotalCacheHits; +static int32_t gTotalCacheMisses; +#endif + +static bool run_single_benchmark(const SkString& inputPath, + sk_tools::PictureBenchmark& benchmark) { + SkFILEStream inputStream; + + inputStream.setPath(inputPath.c_str()); + if (!inputStream.isValid()) { + SkString err; + err.printf("Could not open file %s\n", inputPath.c_str()); + gLogger.logError(err); + return false; + } + + SkDiscardableMemoryPool* pool = SkGetGlobalDiscardableMemoryPool(); + // Since the old picture has been deleted, all pixels should be cleared. + SkASSERT(pool->getRAMUsed() == 0); + if (FLAGS_countRAM) { + pool->setRAMBudget(SK_MaxU32); + // Set the limit to max, so all pixels will be kept + } + + SkPicture::InstallPixelRefProc proc; + if (FLAGS_deferImageDecoding) { + proc = &sk_tools::LazyDecodeBitmap; + } else { + proc = &SkImageDecoder::DecodeMemory; + } + SkAutoTUnref<SkPicture> picture(SkPicture::CreateFromStream(&inputStream, proc)); + + if (NULL == picture.get()) { + SkString err; + err.printf("Could not read an SkPicture from %s\n", inputPath.c_str()); + gLogger.logError(err); + return false; + } + + SkString filename = SkOSPath::SkBasename(inputPath.c_str()); + + gWriter.bench(filename.c_str(), picture->width(), picture->height()); + + benchmark.run(picture); + +#if SK_LAZY_CACHE_STATS + if (FLAGS_trackDeferredCaching) { + int cacheHits = pool->getCacheHits(); + int cacheMisses = pool->getCacheMisses(); + pool->resetCacheHitsAndMisses(); + SkString hitString; + hitString.printf("Cache hit rate: %f\n", (double) cacheHits / (cacheHits + cacheMisses)); + gLogger.logProgress(hitString); + gTotalCacheHits += cacheHits; + gTotalCacheMisses += cacheMisses; + } +#endif + if (FLAGS_countRAM) { + SkString ramCount("RAM used for bitmaps: "); + size_t bytes = pool->getRAMUsed(); + if (bytes > 1024) { + size_t kb = bytes / 1024; + if (kb > 1024) { + size_t mb = kb / 1024; + ramCount.appendf("%zi MB\n", mb); + } else { + ramCount.appendf("%zi KB\n", kb); + } + } else { + ramCount.appendf("%zi bytes\n", bytes); + } + gLogger.logProgress(ramCount); + } + + return true; +} + +static void setup_benchmark(sk_tools::PictureBenchmark* benchmark) { + sk_tools::PictureRenderer::DrawFilterFlags drawFilters[SkDrawFilter::kTypeCount]; + sk_bzero(drawFilters, sizeof(drawFilters)); + + if (FLAGS_filter.count() > 0) { + const char* filters = FLAGS_filter[0]; + const char* colon = strchr(filters, ':'); + if (colon) { + int32_t type = -1; + size_t typeLen = colon - filters; + for (size_t tIndex = 0; tIndex < kFilterTypesCount; ++tIndex) { + if (typeLen == strlen(gFilterTypes[tIndex]) + && !strncmp(filters, gFilterTypes[tIndex], typeLen)) { + type = SkToS32(tIndex); + break; + } + } + if (type < 0) { + SkString err; + err.printf("Unknown type for --filter %s\n", filters); + gLogger.logError(err); + exit(-1); + } + int flag = -1; + size_t flagLen = strlen(filters) - typeLen - 1; + for (size_t fIndex = 0; fIndex < kFilterFlagsCount; ++fIndex) { + if (flagLen == strlen(gFilterFlags[fIndex]) + && !strncmp(colon + 1, gFilterFlags[fIndex], flagLen)) { + flag = 1 << fIndex; + break; + } + } + if (flag < 0) { + SkString err; + err.printf("Unknown flag for --filter %s\n", filters); + gLogger.logError(err); + exit(-1); + } + for (int index = 0; index < SkDrawFilter::kTypeCount; ++index) { + if (type != SkDrawFilter::kTypeCount && index != type) { + continue; + } + drawFilters[index] = (sk_tools::PictureRenderer::DrawFilterFlags) + (drawFilters[index] | flag); + } + } else { + SkString err; + err.printf("Unknown arg for --filter %s : missing colon\n", filters); + gLogger.logError(err); + exit(-1); + } + } + + if (FLAGS_timers.count() > 0) { + size_t index = 0; + bool timerWall = false; + bool truncatedTimerWall = false; + bool timerCpu = false; + bool truncatedTimerCpu = false; + bool timerGpu = false; + while (index < strlen(FLAGS_timers[0])) { + switch (FLAGS_timers[0][index]) { + case 'w': + timerWall = true; + break; + case 'c': + timerCpu = true; + break; + case 'W': + truncatedTimerWall = true; + break; + case 'C': + truncatedTimerCpu = true; + break; + case 'g': + timerGpu = true; + break; + default: + SkDebugf("mystery character\n"); + break; + } + index++; + } + benchmark->setTimersToShow(timerWall, truncatedTimerWall, timerCpu, truncatedTimerCpu, + timerGpu); + } + + SkString errorString; + SkAutoTUnref<sk_tools::PictureRenderer> renderer(parseRenderer(errorString, + kBench_PictureTool)); + + if (errorString.size() > 0) { + gLogger.logError(errorString); + } + + if (NULL == renderer.get()) { + exit(-1); + } + + if (FLAGS_timeIndividualTiles) { + if (FLAGS_multi > 1) { + gLogger.logError("Cannot time individual tiles with more than one thread.\n"); + exit(-1); + } + sk_tools::TiledPictureRenderer* tiledRenderer = renderer->getTiledRenderer(); + if (NULL == tiledRenderer) { + gLogger.logError("--timeIndividualTiles requires tiled rendering.\n"); + exit(-1); + } + if (!tiledRenderer->supportsTimingIndividualTiles()) { + gLogger.logError("This renderer does not support --timeIndividualTiles.\n"); + exit(-1); + } + benchmark->setTimeIndividualTiles(true); + } + + benchmark->setPurgeDecodedTex(FLAGS_purgeDecodedTex); + benchmark->setPreprocess(FLAGS_preprocess); + + if (FLAGS_readPath.count() < 1) { + gLogger.logError(".skp files or directories are required.\n"); + exit(-1); + } + + renderer->setDrawFilters(drawFilters, filtersName(drawFilters)); + if (FLAGS_logPerIter) { + benchmark->setTimerResultType(TimerData::kPerIter_Result); + } else if (FLAGS_min) { + benchmark->setTimerResultType(TimerData::kMin_Result); + } else { + benchmark->setTimerResultType(TimerData::kAvg_Result); + } + benchmark->setRenderer(renderer); + benchmark->setRepeats(FLAGS_repeat); + benchmark->setWriter(&gWriter); +} + +static int process_input(const char* input, + sk_tools::PictureBenchmark& benchmark) { + SkString inputAsSkString(input); + SkOSFile::Iter iter(input, "skp"); + SkString inputFilename; + int failures = 0; + if (iter.next(&inputFilename)) { + do { + SkString inputPath = SkOSPath::SkPathJoin(input, inputFilename.c_str()); + if (!run_single_benchmark(inputPath, benchmark)) { + ++failures; + } + } while(iter.next(&inputFilename)); + } else if (SkStrEndsWith(input, ".skp")) { + if (!run_single_benchmark(inputAsSkString, benchmark)) { + ++failures; + } + } else { + SkString warning; + warning.printf("Warning: skipping %s\n", input); + gLogger.logError(warning); + } + return failures; +} + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + SetupCrashHandler(); + SkString usage; + usage.printf("Time drawing .skp files.\n" + "\tPossible arguments for --filter: [%s]\n\t\t[%s]", + filterTypesUsage().c_str(), filterFlagsUsage().c_str()); + SkCommandLineFlags::SetUsage(usage.c_str()); + SkCommandLineFlags::Parse(argc, argv); + + if (FLAGS_repeat < 1) { + SkString error; + error.printf("--repeats must be >= 1. Was %i\n", FLAGS_repeat); + gLogger.logError(error); + exit(-1); + } + + if (FLAGS_logFile.count() == 1) { + if (!gLogger.SetLogFile(FLAGS_logFile[0])) { + SkString str; + str.printf("Could not open %s for writing.\n", FLAGS_logFile[0]); + gLogger.logError(str); + // TODO(borenet): We're disabling this for now, due to + // write-protected Android devices. The very short-term + // solution is to ignore the fact that we have no log file. + //exit(-1); + } + } + + SkAutoTDelete<PictureJSONResultsWriter> jsonWriter; + if (FLAGS_jsonLog.count() == 1) { + jsonWriter.reset(SkNEW(PictureJSONResultsWriter(FLAGS_jsonLog[0]))); + gWriter.add(jsonWriter.get()); + } + + gWriter.add(&gLogWriter); + + +#if SK_ENABLE_INST_COUNT + gPrintInstCount = true; +#endif + SkAutoGraphics ag; + + sk_tools::PictureBenchmark benchmark; + + setup_benchmark(&benchmark); + + int failures = 0; + for (int i = 0; i < FLAGS_readPath.count(); ++i) { + failures += process_input(FLAGS_readPath[i], benchmark); + } + + if (failures != 0) { + SkString err; + err.printf("Failed to run %i benchmarks.\n", failures); + gLogger.logError(err); + return 1; + } +#if SK_LAZY_CACHE_STATS + if (FLAGS_trackDeferredCaching) { + SkDebugf("Total cache hit rate: %f\n", + (double) gTotalCacheHits / (gTotalCacheHits + gTotalCacheMisses)); + } +#endif + gWriter.end(); + return 0; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/bench_playback.cpp b/chromium/third_party/skia/tools/bench_playback.cpp new file mode 100644 index 00000000000..26fa1c7ee8d --- /dev/null +++ b/chromium/third_party/skia/tools/bench_playback.cpp @@ -0,0 +1,156 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkCommandLineFlags.h" +#include "SkForceLinking.h" +#include "SkGraphics.h" +#include "SkOSFile.h" +#include "SkPicture.h" +#include "SkPictureRecorder.h" +#include "SkStream.h" +#include "SkString.h" + +#include "../include/record/SkRecording.h" + +#include "BenchTimer.h" +#include "Stats.h" + +typedef WallTimer Timer; + +__SK_FORCE_IMAGE_DECODER_LINKING; + +DEFINE_string2(skps, r, "skps", "Directory containing SKPs to playback."); +DEFINE_int32(samples, 10, "Gather this many samples of each picture playback."); +DEFINE_bool(skr, false, "Play via SkRecord instead of SkPicture."); +DEFINE_int32(tile, 1000000000, "Simulated tile size."); +DEFINE_string(match, "", "The usual filters on file names of SKPs to bench."); +DEFINE_string(timescale, "ms", "Print times in ms, us, or ns"); +DEFINE_int32(verbose, 0, "0: print min sample; " + "1: print min, mean, max and noise indication " + "2: print all samples"); + +static double timescale() { + if (FLAGS_timescale.contains("us")) return 1000; + if (FLAGS_timescale.contains("ns")) return 1000000; + return 1; +} + +static SkPicture* rerecord_with_tilegrid(SkPicture& src) { + SkTileGridFactory::TileGridInfo info; + info.fTileInterval.set(FLAGS_tile, FLAGS_tile); + info.fMargin.setEmpty(); + info.fOffset.setZero(); + SkTileGridFactory factory(info); + + SkPictureRecorder recorder; + src.draw(recorder.beginRecording(src.width(), src.height(), &factory)); + return recorder.endRecording(); +} + +static EXPERIMENTAL::SkPlayback* rerecord_with_skr(SkPicture& src) { + EXPERIMENTAL::SkRecording recording(src.width(), src.height()); + src.draw(recording.canvas()); + return recording.releasePlayback(); +} + +static void draw(const EXPERIMENTAL::SkPlayback& skr, const SkPicture& skp, SkCanvas* canvas) { + if (FLAGS_skr) { + skr.draw(canvas); + } else { + skp.draw(canvas); + } +} + +static void bench(SkPMColor* scratch, SkPicture& src, const char* name) { + SkAutoTUnref<SkPicture> picture(rerecord_with_tilegrid(src)); + SkAutoTDelete<EXPERIMENTAL::SkPlayback> record(rerecord_with_skr(src)); + + SkAutoTDelete<SkCanvas> canvas(SkCanvas::NewRasterDirectN32(src.width(), + src.height(), + scratch, + src.width() * sizeof(SkPMColor))); + canvas->clipRect(SkRect::MakeWH(SkIntToScalar(FLAGS_tile), SkIntToScalar(FLAGS_tile))); + + // Draw once to warm any caches. The first sample otherwise can be very noisy. + draw(*record, *picture, canvas.get()); + + Timer timer; + SkAutoTMalloc<double> samples(FLAGS_samples); + for (int i = 0; i < FLAGS_samples; i++) { + // We assume timer overhead (typically, ~30ns) is insignificant + // compared to draw runtime (at least ~100us, usually several ms). + timer.start(timescale()); + draw(*record, *picture, canvas.get()); + timer.end(); + samples[i] = timer.fWall; + } + + Stats stats(samples.get(), FLAGS_samples); + if (FLAGS_verbose == 0) { + printf("%g\t%s\n", stats.min, name); + } else if (FLAGS_verbose == 1) { + // Get a rough idea of how noisy the measurements were. + const double noisePercent = 100 * sqrt(stats.var) / stats.mean; + printf("%g\t%g\t%g\t±%.0f%%\t%s\n", stats.min, stats.mean, stats.max, noisePercent, name); + } else if (FLAGS_verbose == 2) { + printf("%s", name); + for (int i = 0; i < FLAGS_samples; i++) { + printf("\t%g", samples[i]); + } + printf("\n"); + } +} + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + SkCommandLineFlags::Parse(argc, argv); + SkAutoGraphics autoGraphics; + + // We share a single scratch bitmap among benches to reduce the profile noise from allocation. + static const int kMaxArea = 209825221; // tabl_mozilla is this big. + SkAutoTMalloc<SkPMColor> scratch(kMaxArea); + + SkOSFile::Iter it(FLAGS_skps[0], ".skp"); + SkString filename; + bool failed = false; + while (it.next(&filename)) { + if (SkCommandLineFlags::ShouldSkip(FLAGS_match, filename.c_str())) { + continue; + } + + const SkString path = SkOSPath::SkPathJoin(FLAGS_skps[0], filename.c_str()); + + SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path.c_str())); + if (!stream) { + SkDebugf("Could not read %s.\n", path.c_str()); + failed = true; + continue; + } + SkAutoTUnref<SkPicture> src(SkPicture::CreateFromStream(stream)); + if (!src) { + SkDebugf("Could not read %s as an SkPicture.\n", path.c_str()); + failed = true; + continue; + } + + if (src->width() * src->height() > kMaxArea) { + SkDebugf("%s (%dx%d) is larger than hardcoded scratch bitmap (%dpx).\n", + path.c_str(), src->width(), src->height(), kMaxArea); + failed = true; + continue; + } + + bench(scratch.get(), *src, filename.c_str()); + } + return failed ? 1 : 0; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/bench_record.cpp b/chromium/third_party/skia/tools/bench_record.cpp new file mode 100644 index 00000000000..0024c2ccdbe --- /dev/null +++ b/chromium/third_party/skia/tools/bench_record.cpp @@ -0,0 +1,176 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkCommandLineFlags.h" +#include "SkForceLinking.h" +#include "SkGraphics.h" +#include "SkOSFile.h" +#include "SkPicture.h" +#include "SkPictureRecorder.h" +#include "SkStream.h" +#include "SkString.h" + +#include "BenchTimer.h" +#include "LazyDecodeBitmap.h" +#include "Stats.h" + +typedef WallTimer Timer; + +__SK_FORCE_IMAGE_DECODER_LINKING; + +DEFINE_string2(skps, r, "skps", "Directory containing SKPs to read and re-record."); +DEFINE_int32(samples, 10, "Number of times to re-record each SKP."); +DEFINE_int32(tileGridSize, 512, "Set the tile grid size. Has no effect if bbh is not set to tilegrid."); +DEFINE_string(bbh, "", "Turn on the bbh and select the type, one of rtree, tilegrid, quadtree"); +DEFINE_bool(skr, false, "Record SKR instead of SKP."); +DEFINE_string(match, "", "The usual filters on file names of SKPs to bench."); +DEFINE_string(timescale, "us", "Print times in ms, us, or ns"); +DEFINE_double(overheadGoal, 0.0001, + "Try to make timer overhead at most this fraction of our sample measurements."); +DEFINE_int32(verbose, 0, "0: print min sample; " + "1: print min, mean, max and noise indication " + "2: print all samples"); + +static double timescale() { + if (FLAGS_timescale.contains("us")) return 1000; + if (FLAGS_timescale.contains("ns")) return 1000000; + return 1; +} + +static SkBBHFactory* parse_FLAGS_bbh() { + if (FLAGS_bbh.isEmpty()) { + return NULL; + } + + if (FLAGS_bbh.contains("rtree")) { + return SkNEW(SkRTreeFactory); + } + if (FLAGS_bbh.contains("tilegrid")) { + SkTileGridFactory::TileGridInfo info; + info.fTileInterval.set(FLAGS_tileGridSize, FLAGS_tileGridSize); + info.fMargin.setEmpty(); + info.fOffset.setZero(); + return SkNEW_ARGS(SkTileGridFactory, (info)); + } + if (FLAGS_bbh.contains("quadtree")) { + return SkNEW(SkQuadTreeFactory); + } + SkDebugf("Invalid bbh type %s, must be one of rtree, tilegrid, quadtree.\n", FLAGS_bbh[0]); + return NULL; +} + +static void rerecord(const SkPicture& src, SkBBHFactory* bbhFactory) { + SkPictureRecorder recorder; + if (FLAGS_skr) { + src.draw(recorder.EXPERIMENTAL_beginRecording(src.width(), src.height(), bbhFactory)); + } else { + src.draw(recorder.beginRecording(src.width(), src.height(), bbhFactory)); + } + SkAutoTUnref<SkPicture> pic(recorder.endRecording()); +} + +static void bench_record(const SkPicture& src, + const double timerOverhead, + const char* name, + SkBBHFactory* bbhFactory) { + // Rerecord once to warm up any caches. Otherwise the first sample can be very noisy. + rerecord(src, bbhFactory); + + // Rerecord once to see how many times we should loop to make timer overhead insignificant. + Timer timer; + do { + timer.start(timescale()); + rerecord(src, bbhFactory); + timer.end(); + } while (timer.fWall < timerOverhead); // Loop just in case something bizarre happens. + + // We want (timer overhead / measurement) to be less than FLAGS_overheadGoal. + // So in each sample, we'll loop enough times to have made that true for our first measurement. + const int loops = (int)ceil(timerOverhead / timer.fWall / FLAGS_overheadGoal); + + SkAutoTMalloc<double> samples(FLAGS_samples); + for (int i = 0; i < FLAGS_samples; i++) { + timer.start(timescale()); + for (int j = 0; j < loops; j++) { + rerecord(src, bbhFactory); + } + timer.end(); + samples[i] = timer.fWall / loops; + } + + Stats stats(samples.get(), FLAGS_samples); + if (FLAGS_verbose == 0) { + printf("%g\t%s\n", stats.min, name); + } else if (FLAGS_verbose == 1) { + // Get a rough idea of how noisy the measurements were. + const double noisePercent = 100 * sqrt(stats.var) / stats.mean; + printf("%g\t%g\t%g\t±%.0f%%\t%s\n", stats.min, stats.mean, stats.max, noisePercent, name); + } else if (FLAGS_verbose == 2) { + printf("%s", name); + for (int i = 0; i < FLAGS_samples; i++) { + printf("\t%g", samples[i]); + } + printf("\n"); + } +} + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + SkCommandLineFlags::Parse(argc, argv); + SkAutoGraphics autoGraphics; + + if (FLAGS_bbh.count() > 1) { + SkDebugf("Multiple bbh arguments supplied.\n"); + return 1; + } + + SkAutoTDelete<SkBBHFactory> bbhFactory(parse_FLAGS_bbh()); + + // Each run will use this timer overhead estimate to guess how many times it should run. + static const int kOverheadLoops = 10000000; + Timer timer; + double overheadEstimate = 0.0; + for (int i = 0; i < kOverheadLoops; i++) { + timer.start(timescale()); + timer.end(); + overheadEstimate += timer.fWall; + } + overheadEstimate /= kOverheadLoops; + + SkOSFile::Iter it(FLAGS_skps[0], ".skp"); + SkString filename; + bool failed = false; + while (it.next(&filename)) { + if (SkCommandLineFlags::ShouldSkip(FLAGS_match, filename.c_str())) { + continue; + } + + const SkString path = SkOSPath::SkPathJoin(FLAGS_skps[0], filename.c_str()); + + SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path.c_str())); + if (!stream) { + SkDebugf("Could not read %s.\n", path.c_str()); + failed = true; + continue; + } + SkAutoTUnref<SkPicture> src( + SkPicture::CreateFromStream(stream, sk_tools::LazyDecodeBitmap)); + if (!src) { + SkDebugf("Could not read %s as an SkPicture.\n", path.c_str()); + failed = true; + continue; + } + bench_record(*src, overheadEstimate, filename.c_str(), bbhFactory.get()); + } + return failed ? 1 : 0; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/bug_chomper/res/favicon.ico b/chromium/third_party/skia/tools/bug_chomper/res/favicon.ico Binary files differnew file mode 100644 index 00000000000..e7440c75127 --- /dev/null +++ b/chromium/third_party/skia/tools/bug_chomper/res/favicon.ico diff --git a/chromium/third_party/skia/tools/bug_chomper/res/style.css b/chromium/third_party/skia/tools/bug_chomper/res/style.css new file mode 100644 index 00000000000..726e5aecd56 --- /dev/null +++ b/chromium/third_party/skia/tools/bug_chomper/res/style.css @@ -0,0 +1,72 @@ +table#buglist { + border-collapse: collapse; + border-style: solid; + border-color: rgba(0, 0, 0, 1.0); + border-width: 3px; + width: 80%; + margin-left: 10%; + margin-right: 10%; +} + +tr { + border-color: rgba(0, 0, 0, 1.0); + border-style: dashed; + border-width: 1px 3px; +} + +tr.priority_Critical { + background-color: rgba(255, 0, 0, 0.3); +} + +tr.priority_High { + background-color: rgba(255, 165, 0, 0.3); +} + +tr.priority_Medium { + background-color: rgba(255, 255, 0, 0.3); +} + +tr.priority_Low { + background-color: rgba(0, 255, 0, 0.3); +} + +tr.priority_Never { + background-color: rgba(190, 190, 190, 0.3); +} + +tbody { + background-color: rgba(190, 190, 190, 0.1); +} + +tr.priority_row { + background-color: rgba(190, 190, 190, 0.1); + border-style: solid; +} + +tr.tr_head { + background-color: rgba(190, 190, 190, 0.5); +} + +#table_header { + text-align: center; +} + +td { + padding: 5px; +} + +td.priority_td { + text-align: center; +} + +a { + color: black; +} + +a:visited { + color: black; +} + +a:hover { + text-decoration: none; +}
\ No newline at end of file diff --git a/chromium/third_party/skia/tools/bug_chomper/res/third_party/jquery.tablednd.js b/chromium/third_party/skia/tools/bug_chomper/res/third_party/jquery.tablednd.js new file mode 100644 index 00000000000..869f94effc8 --- /dev/null +++ b/chromium/third_party/skia/tools/bug_chomper/res/third_party/jquery.tablednd.js @@ -0,0 +1,314 @@ +/** + * TableDnD plug-in for JQuery, allows you to drag and drop table rows + * You can set up various options to control how the system will work + * Copyright © Denis Howlett <denish@isocra.com> + * Licensed like jQuery, see http://docs.jquery.com/License. + * + * Configuration options: + * + * onDragStyle + * This is the style that is assigned to the row during drag. There are limitations to the styles that can be + * associated with a row (such as you can't assign a border—well you can, but it won't be + * displayed). (So instead consider using onDragClass.) The CSS style to apply is specified as + * a map (as used in the jQuery css(...) function). + * onDropStyle + * This is the style that is assigned to the row when it is dropped. As for onDragStyle, there are limitations + * to what you can do. Also this replaces the original style, so again consider using onDragClass which + * is simply added and then removed on drop. + * onDragClass + * This class is added for the duration of the drag and then removed when the row is dropped. It is more + * flexible than using onDragStyle since it can be inherited by the row cells and other content. The default + * is class is tDnD_whileDrag. So to use the default, simply customise this CSS class in your + * stylesheet. + * onDrop + * Pass a function that will be called when the row is dropped. The function takes 2 parameters: the table + * and the row that was dropped. You can work out the new order of the rows by using + * table.rows. + * onDragStart + * Pass a function that will be called when the user starts dragging. The function takes 2 parameters: the + * table and the row which the user has started to drag. + * onAllowDrop + * Pass a function that will be called as a row is over another row. If the function returns true, allow + * dropping on that row, otherwise not. The function takes 2 parameters: the dragged row and the row under + * the cursor. It returns a boolean: true allows the drop, false doesn't allow it. + * scrollAmount + * This is the number of pixels to scroll if the user moves the mouse cursor to the top or bottom of the + * window. The page should automatically scroll up or down as appropriate (tested in IE6, IE7, Safari, FF2, + * FF3 beta) + * + * Other ways to control behaviour: + * + * Add class="nodrop" to any rows for which you don't want to allow dropping, and class="nodrag" to any rows + * that you don't want to be draggable. + * + * Inside the onDrop method you can also call $.tableDnD.serialize() this returns a string of the form + * <tableID>[]=<rowID1>&<tableID>[]=<rowID2> so that you can send this back to the server. The table must have + * an ID as must all the rows. + * + * Known problems: + * - Auto-scoll has some problems with IE7 (it scrolls even when it shouldn't), work-around: set scrollAmount to 0 + * + * Version 0.2: 2008-02-20 First public version + * Version 0.3: 2008-02-07 Added onDragStart option + * Made the scroll amount configurable (default is 5 as before) + * Version 0.4: 2008-03-15 Changed the noDrag/noDrop attributes to nodrag/nodrop classes + * Added onAllowDrop to control dropping + * Fixed a bug which meant that you couldn't set the scroll amount in both directions + * Added serialise method + */ +jQuery.tableDnD = { + /** Keep hold of the current table being dragged */ + currentTable : null, + /** Keep hold of the current drag object if any */ + dragObject: null, + /** The current mouse offset */ + mouseOffset: null, + /** Remember the old value of Y so that we don't do too much processing */ + oldY: 0, + + /** Actually build the structure */ + build: function(options) { + // Make sure options exists + options = options || {}; + // Set up the defaults if any + + this.each(function() { + // Remember the options + this.tableDnDConfig = { + onDragStyle: options.onDragStyle, + onDropStyle: options.onDropStyle, + // Add in the default class for whileDragging + onDragClass: options.onDragClass ? options.onDragClass : "tDnD_whileDrag", + onDrop: options.onDrop, + onDragStart: options.onDragStart, + scrollAmount: options.scrollAmount ? options.scrollAmount : 5 + }; + // Now make the rows draggable + jQuery.tableDnD.makeDraggable(this); + }); + + // Now we need to capture the mouse up and mouse move event + // We can use bind so that we don't interfere with other event handlers + jQuery(document) + .bind('mousemove', jQuery.tableDnD.mousemove) + .bind('mouseup', jQuery.tableDnD.mouseup); + + // Don't break the chain + return this; + }, + + /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */ + makeDraggable: function(table) { + // Now initialise the rows + var rows = table.rows; //getElementsByTagName("tr") + var config = table.tableDnDConfig; + for (var i=0; i<rows.length; i++) { + // To make non-draggable rows, add the nodrag class (eg for Category and Header rows) + // inspired by John Tarr and Famic + var nodrag = $(rows[i]).hasClass("nodrag"); + if (! nodrag) { //There is no NoDnD attribute on rows I want to drag + jQuery(rows[i]).mousedown(function(ev) { + if (ev.target.tagName == "TD") { + jQuery.tableDnD.dragObject = this; + jQuery.tableDnD.currentTable = table; + jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev); + if (config.onDragStart) { + // Call the onDrop method if there is one + config.onDragStart(table, this); + } + return false; + } + }).css("cursor", "move"); // Store the tableDnD object + } + } + }, + + /** Get the mouse coordinates from the event (allowing for browser differences) */ + mouseCoords: function(ev){ + if(ev.pageX || ev.pageY){ + return {x:ev.pageX, y:ev.pageY}; + } + return { + x:ev.clientX + document.body.scrollLeft - document.body.clientLeft, + y:ev.clientY + document.body.scrollTop - document.body.clientTop + }; + }, + + /** Given a target element and a mouse event, get the mouse offset from that element. + To do this we need the element's position and the mouse position */ + getMouseOffset: function(target, ev) { + ev = ev || window.event; + + var docPos = this.getPosition(target); + var mousePos = this.mouseCoords(ev); + return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y}; + }, + + /** Get the position of an element by going up the DOM tree and adding up all the offsets */ + getPosition: function(e){ + var left = 0; + var top = 0; + /** Safari fix -- thanks to Luis Chato for this! */ + if (e.offsetHeight == 0) { + /** Safari 2 doesn't correctly grab the offsetTop of a table row + this is detailed here: + http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/ + the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild. + note that firefox will return a text node as a first child, so designing a more thorough + solution may need to take that into account, for now this seems to work in firefox, safari, ie */ + e = e.firstChild; // a table cell + } + + while (e.offsetParent){ + left += e.offsetLeft; + top += e.offsetTop; + e = e.offsetParent; + } + + left += e.offsetLeft; + top += e.offsetTop; + + return {x:left, y:top}; + }, + + mousemove: function(ev) { + if (jQuery.tableDnD.dragObject == null) { + return; + } + + var dragObj = jQuery(jQuery.tableDnD.dragObject); + var config = jQuery.tableDnD.currentTable.tableDnDConfig; + var mousePos = jQuery.tableDnD.mouseCoords(ev); + var y = mousePos.y - jQuery.tableDnD.mouseOffset.y; + //auto scroll the window + var yOffset = window.pageYOffset; + if (document.all) { + // Windows version + //yOffset=document.body.scrollTop; + if (typeof document.compatMode != 'undefined' && + document.compatMode != 'BackCompat') { + yOffset = document.documentElement.scrollTop; + } + else if (typeof document.body != 'undefined') { + yOffset=document.body.scrollTop; + } + + } + + if (mousePos.y-yOffset < config.scrollAmount) { + window.scrollBy(0, -config.scrollAmount); + } else { + var windowHeight = window.innerHeight ? window.innerHeight + : document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight; + if (windowHeight-(mousePos.y-yOffset) < config.scrollAmount) { + window.scrollBy(0, config.scrollAmount); + } + } + + + if (y != jQuery.tableDnD.oldY) { + // work out if we're going up or down... + var movingDown = y > jQuery.tableDnD.oldY; + // update the old value + jQuery.tableDnD.oldY = y; + // update the style to show we're dragging + if (config.onDragClass) { + dragObj.addClass(config.onDragClass); + } else { + dragObj.css(config.onDragStyle); + } + // If we're over a row then move the dragged row to there so that the user sees the + // effect dynamically + var currentRow = jQuery.tableDnD.findDropTargetRow(dragObj, y); + if (currentRow) { + // TODO worry about what happens when there are multiple TBODIES + if (movingDown && jQuery.tableDnD.dragObject != currentRow) { + jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow.nextSibling); + } else if (! movingDown && jQuery.tableDnD.dragObject != currentRow) { + jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow); + } + } + } + + return false; + }, + + /** We're only worried about the y position really, because we can only move rows up and down */ + findDropTargetRow: function(draggedRow, y) { + var rows = jQuery.tableDnD.currentTable.rows; + for (var i=0; i<rows.length; i++) { + var row = rows[i]; + var rowY = this.getPosition(row).y; + var rowHeight = parseInt(row.offsetHeight)/2; + if (row.offsetHeight == 0) { + rowY = this.getPosition(row.firstChild).y; + rowHeight = parseInt(row.firstChild.offsetHeight)/2; + } + // Because we always have to insert before, we need to offset the height a bit + if ((y > rowY - rowHeight) && (y < (rowY + rowHeight))) { + // that's the row we're over + // If it's the same as the current row, ignore it + if (row == draggedRow) {return null;} + var config = jQuery.tableDnD.currentTable.tableDnDConfig; + if (config.onAllowDrop) { + if (config.onAllowDrop(draggedRow, row)) { + return row; + } else { + return null; + } + } else { + // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic) + var nodrop = $(row).hasClass("nodrop"); + if (! nodrop) { + return row; + } else { + return null; + } + } + return row; + } + } + return null; + }, + + mouseup: function(e) { + if (jQuery.tableDnD.currentTable && jQuery.tableDnD.dragObject) { + var droppedRow = jQuery.tableDnD.dragObject; + var config = jQuery.tableDnD.currentTable.tableDnDConfig; + // If we have a dragObject, then we need to release it, + // The row will already have been moved to the right place so we just reset stuff + if (config.onDragClass) { + jQuery(droppedRow).removeClass(config.onDragClass); + } else { + jQuery(droppedRow).css(config.onDropStyle); + } + jQuery.tableDnD.dragObject = null; + if (config.onDrop) { + // Call the onDrop method if there is one + config.onDrop(jQuery.tableDnD.currentTable, droppedRow); + } + jQuery.tableDnD.currentTable = null; // let go of the table too + } + }, + + serialize: function() { + if (jQuery.tableDnD.currentTable) { + var result = ""; + var tableId = jQuery.tableDnD.currentTable.id; + var rows = jQuery.tableDnD.currentTable.rows; + for (var i=0; i<rows.length; i++) { + if (result.length > 0) result += "&"; + result += tableId + '[]=' + rows[i].id; + } + return result; + } else { + return "Error: No Table id set, you need to set an id on your table and every row"; + } + } +} + +jQuery.fn.extend( + { + tableDnD : jQuery.tableDnD.build + } +);
\ No newline at end of file diff --git a/chromium/third_party/skia/tools/bug_chomper/run_server.sh b/chromium/third_party/skia/tools/bug_chomper/run_server.sh new file mode 100755 index 00000000000..a6cb4531aa0 --- /dev/null +++ b/chromium/third_party/skia/tools/bug_chomper/run_server.sh @@ -0,0 +1,23 @@ +if [[ -z `which go` ]]; then + echo "Please install Go before running the server." + exit 1 +fi + +if [[ -z "$GOPATH" ]]; then + echo "GOPATH environment variable is not set. Please see" + echo "http://golang.org/doc/code.html#GOPATH for more information." + exit 1 +fi + +go get github.com/gorilla/securecookie +go get code.google.com/p/goauth2/oauth + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd $DIR + +if [[ ! -f oauth_client_secret.json ]]; then + gsutil cp gs://chromium-skia-gm/bugchomper/oauth_client_secret.json . +fi + +GOPATH="$GOPATH:$DIR" go run $DIR/src/server/server.go $@ + diff --git a/chromium/third_party/skia/tools/bug_chomper/src/issue_tracker/issue_tracker.go b/chromium/third_party/skia/tools/bug_chomper/src/issue_tracker/issue_tracker.go new file mode 100644 index 00000000000..e4854f5ee6d --- /dev/null +++ b/chromium/third_party/skia/tools/bug_chomper/src/issue_tracker/issue_tracker.go @@ -0,0 +1,303 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* + Utilities for interacting with the GoogleCode issue tracker. + + Example usage: + issueTracker := issue_tracker.MakeIssueTraker(myOAuthConfigFile) + authURL := issueTracker.MakeAuthRequestURL() + // Visit the authURL to obtain an authorization code. + issueTracker.UpgradeCode(code) + // Now issueTracker can be used to retrieve and edit issues. +*/ +package issue_tracker + +import ( + "bytes" + "code.google.com/p/goauth2/oauth" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "strings" +) + +// BugPriorities are the possible values for "Priority-*" labels for issues. +var BugPriorities = []string{"Critical", "High", "Medium", "Low", "Never"} + +var apiScope = []string{ + "https://www.googleapis.com/auth/projecthosting", + "https://www.googleapis.com/auth/userinfo.email", +} + +const issueApiURL = "https://www.googleapis.com/projecthosting/v2/projects/" +const issueURL = "https://code.google.com/p/skia/issues/detail?id=" +const personApiURL = "https://www.googleapis.com/userinfo/v2/me" + +// Enum for determining whether a label has been added, removed, or is +// unchanged. +const ( + labelAdded = iota + labelRemoved + labelUnchanged +) + +// loadOAuthConfig reads the OAuth given config file path and returns an +// appropriate oauth.Config. +func loadOAuthConfig(oauthConfigFile string) (*oauth.Config, error) { + errFmt := "failed to read OAuth config file: %s" + fileContents, err := ioutil.ReadFile(oauthConfigFile) + if err != nil { + return nil, fmt.Errorf(errFmt, err) + } + var decodedJson map[string]struct { + AuthURL string `json:"auth_uri"` + ClientId string `json:"client_id"` + ClientSecret string `json:"client_secret"` + TokenURL string `json:"token_uri"` + } + if err := json.Unmarshal(fileContents, &decodedJson); err != nil { + return nil, fmt.Errorf(errFmt, err) + } + config, ok := decodedJson["web"] + if !ok { + return nil, fmt.Errorf(errFmt, err) + } + return &oauth.Config{ + ClientId: config.ClientId, + ClientSecret: config.ClientSecret, + Scope: strings.Join(apiScope, " "), + AuthURL: config.AuthURL, + TokenURL: config.TokenURL, + }, nil +} + +// Issue contains information about an issue. +type Issue struct { + Id int `json:"id"` + Project string `json:"projectId"` + Title string `json:"title"` + Labels []string `json:"labels"` +} + +// URL returns the URL of a given issue. +func (i Issue) URL() string { + return issueURL + strconv.Itoa(i.Id) +} + +// IssueList represents a list of issues from the IssueTracker. +type IssueList struct { + TotalResults int `json:"totalResults"` + Items []*Issue `json:"items"` +} + +// IssueTracker is the primary point of contact with the issue tracker, +// providing methods for authenticating to and interacting with it. +type IssueTracker struct { + OAuthConfig *oauth.Config + OAuthTransport *oauth.Transport +} + +// MakeIssueTracker creates and returns an IssueTracker with authentication +// configuration from the given authConfigFile. +func MakeIssueTracker(authConfigFile string, redirectURL string) (*IssueTracker, error) { + oauthConfig, err := loadOAuthConfig(authConfigFile) + if err != nil { + return nil, fmt.Errorf( + "failed to create IssueTracker: %s", err) + } + oauthConfig.RedirectURL = redirectURL + return &IssueTracker{ + OAuthConfig: oauthConfig, + OAuthTransport: &oauth.Transport{Config: oauthConfig}, + }, nil +} + +// MakeAuthRequestURL returns an authentication request URL which can be used +// to obtain an authorization code via user sign-in. +func (it IssueTracker) MakeAuthRequestURL() string { + // NOTE: Need to add XSRF protection if we ever want to run this on a public + // server. + return it.OAuthConfig.AuthCodeURL(it.OAuthConfig.RedirectURL) +} + +// IsAuthenticated determines whether the IssueTracker has sufficient +// permissions to retrieve and edit Issues. +func (it IssueTracker) IsAuthenticated() bool { + return it.OAuthTransport.Token != nil +} + +// UpgradeCode exchanges the single-use authorization code, obtained by +// following the URL obtained from IssueTracker.MakeAuthRequestURL, for a +// multi-use, session token. This is required before IssueTracker can retrieve +// and edit issues. +func (it *IssueTracker) UpgradeCode(code string) error { + token, err := it.OAuthTransport.Exchange(code) + if err == nil { + it.OAuthTransport.Token = token + return nil + } else { + return fmt.Errorf( + "failed to exchange single-user auth code: %s", err) + } +} + +// GetLoggedInUser retrieves the email address of the authenticated user. +func (it IssueTracker) GetLoggedInUser() (string, error) { + errFmt := "error retrieving user email: %s" + if !it.IsAuthenticated() { + return "", fmt.Errorf(errFmt, "User is not authenticated!") + } + resp, err := it.OAuthTransport.Client().Get(personApiURL) + if err != nil { + return "", fmt.Errorf(errFmt, err) + } + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf(errFmt, fmt.Sprintf( + "user data API returned code %d: %v", resp.StatusCode, string(body))) + } + userInfo := struct { + Email string `json:"email"` + }{} + if err := json.Unmarshal(body, &userInfo); err != nil { + return "", fmt.Errorf(errFmt, err) + } + return userInfo.Email, nil +} + +// GetBug retrieves the Issue with the given ID from the IssueTracker. +func (it IssueTracker) GetBug(project string, id int) (*Issue, error) { + errFmt := fmt.Sprintf("error retrieving issue %d: %s", id, "%s") + if !it.IsAuthenticated() { + return nil, fmt.Errorf(errFmt, "user is not authenticated!") + } + requestURL := issueApiURL + project + "/issues/" + strconv.Itoa(id) + resp, err := it.OAuthTransport.Client().Get(requestURL) + if err != nil { + return nil, fmt.Errorf(errFmt, err) + } + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf(errFmt, fmt.Sprintf( + "issue tracker returned code %d:%v", resp.StatusCode, string(body))) + } + var issue Issue + if err := json.Unmarshal(body, &issue); err != nil { + return nil, fmt.Errorf(errFmt, err) + } + return &issue, nil +} + +// GetBugs retrieves all Issues with the given owner from the IssueTracker, +// returning an IssueList. +func (it IssueTracker) GetBugs(project string, owner string) (*IssueList, error) { + errFmt := "error retrieving issues: %s" + if !it.IsAuthenticated() { + return nil, fmt.Errorf(errFmt, "user is not authenticated!") + } + params := map[string]string{ + "owner": url.QueryEscape(owner), + "can": "open", + "maxResults": "9999", + } + requestURL := issueApiURL + project + "/issues?" + first := true + for k, v := range params { + if first { + first = false + } else { + requestURL += "&" + } + requestURL += k + "=" + v + } + resp, err := it.OAuthTransport.Client().Get(requestURL) + if err != nil { + return nil, fmt.Errorf(errFmt, err) + } + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf(errFmt, fmt.Sprintf( + "issue tracker returned code %d:%v", resp.StatusCode, string(body))) + } + + var bugList IssueList + if err := json.Unmarshal(body, &bugList); err != nil { + return nil, fmt.Errorf(errFmt, err) + } + return &bugList, nil +} + +// SubmitIssueChanges creates a comment on the given Issue which modifies it +// according to the contents of the passed-in Issue struct. +func (it IssueTracker) SubmitIssueChanges(issue *Issue, comment string) error { + errFmt := "Error updating issue " + strconv.Itoa(issue.Id) + ": %s" + if !it.IsAuthenticated() { + return fmt.Errorf(errFmt, "user is not authenticated!") + } + oldIssue, err := it.GetBug(issue.Project, issue.Id) + if err != nil { + return fmt.Errorf(errFmt, err) + } + postData := struct { + Content string `json:"content"` + Updates struct { + Title *string `json:"summary"` + Labels []string `json:"labels"` + } `json:"updates"` + }{ + Content: comment, + } + if issue.Title != oldIssue.Title { + postData.Updates.Title = &issue.Title + } + // TODO(borenet): Add other issue attributes, eg. Owner. + labels := make(map[string]int) + for _, label := range issue.Labels { + labels[label] = labelAdded + } + for _, label := range oldIssue.Labels { + if _, ok := labels[label]; ok { + labels[label] = labelUnchanged + } else { + labels[label] = labelRemoved + } + } + labelChanges := make([]string, 0) + for labelName, present := range labels { + if present == labelRemoved { + labelChanges = append(labelChanges, "-"+labelName) + } else if present == labelAdded { + labelChanges = append(labelChanges, labelName) + } + } + if len(labelChanges) > 0 { + postData.Updates.Labels = labelChanges + } + + postBytes, err := json.Marshal(&postData) + if err != nil { + return fmt.Errorf(errFmt, err) + } + requestURL := issueApiURL + issue.Project + "/issues/" + + strconv.Itoa(issue.Id) + "/comments" + resp, err := it.OAuthTransport.Client().Post( + requestURL, "application/json", bytes.NewReader(postBytes)) + if err != nil { + return fmt.Errorf(errFmt, err) + } + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK { + return fmt.Errorf(errFmt, fmt.Sprintf( + "Issue tracker returned code %d:%v", resp.StatusCode, string(body))) + } + return nil +} diff --git a/chromium/third_party/skia/tools/bug_chomper/src/server/server.go b/chromium/third_party/skia/tools/bug_chomper/src/server/server.go new file mode 100644 index 00000000000..9fb21ed594d --- /dev/null +++ b/chromium/third_party/skia/tools/bug_chomper/src/server/server.go @@ -0,0 +1,376 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* + Serves a webpage for easy management of Skia bugs. + + WARNING: This server is NOT secure and should not be made publicly + accessible. +*/ + +package main + +import ( + "encoding/json" + "flag" + "fmt" + "html/template" + "issue_tracker" + "log" + "net/http" + "net/url" + "path" + "path/filepath" + "strconv" + "strings" + "time" +) + +import "github.com/gorilla/securecookie" + +const certFile = "certs/cert.pem" +const keyFile = "certs/key.pem" +const issueComment = "Edited by BugChomper" +const oauthCallbackPath = "/oauth2callback" +const oauthConfigFile = "oauth_client_secret.json" +const defaultPort = 8000 +const localHost = "127.0.0.1" +const maxSessionLen = time.Duration(3600 * time.Second) +const priorityPrefix = "Priority-" +const project = "skia" +const cookieName = "BugChomperCookie" + +var scheme = "http" + +var curdir, _ = filepath.Abs(".") +var templatePath, _ = filepath.Abs("templates") +var templates = template.Must(template.ParseFiles( + path.Join(templatePath, "bug_chomper.html"), + path.Join(templatePath, "submitted.html"), + path.Join(templatePath, "error.html"))) + +var hashKey = securecookie.GenerateRandomKey(32) +var blockKey = securecookie.GenerateRandomKey(32) +var secureCookie = securecookie.New(hashKey, blockKey) + +// SessionState contains data for a given session. +type SessionState struct { + IssueTracker *issue_tracker.IssueTracker + OrigRequestURL string + SessionStart time.Time +} + +// getAbsoluteURL returns the absolute URL of the given Request. +func getAbsoluteURL(r *http.Request) string { + return scheme + "://" + r.Host + r.URL.Path +} + +// getOAuth2CallbackURL returns a callback URL to be used by the OAuth2 login +// page. +func getOAuth2CallbackURL(r *http.Request) string { + return scheme + "://" + r.Host + oauthCallbackPath +} + +func saveSession(session *SessionState, w http.ResponseWriter, r *http.Request) error { + encodedSession, err := secureCookie.Encode(cookieName, session) + if err != nil { + return fmt.Errorf("unable to encode session state: %s", err) + } + cookie := &http.Cookie{ + Name: cookieName, + Value: encodedSession, + Domain: strings.Split(r.Host, ":")[0], + Path: "/", + HttpOnly: true, + } + http.SetCookie(w, cookie) + return nil +} + +// makeSession creates a new session for the Request. +func makeSession(w http.ResponseWriter, r *http.Request) (*SessionState, error) { + log.Println("Creating new session.") + // Create the session state. + issueTracker, err := issue_tracker.MakeIssueTracker( + oauthConfigFile, getOAuth2CallbackURL(r)) + if err != nil { + return nil, fmt.Errorf("unable to create IssueTracker for session: %s", err) + } + session := SessionState{ + IssueTracker: issueTracker, + OrigRequestURL: getAbsoluteURL(r), + SessionStart: time.Now(), + } + + // Encode and store the session state. + if err := saveSession(&session, w, r); err != nil { + return nil, err + } + + return &session, nil +} + +// getSession retrieves the active SessionState or creates and returns a new +// SessionState. +func getSession(w http.ResponseWriter, r *http.Request) (*SessionState, error) { + cookie, err := r.Cookie(cookieName) + if err != nil { + log.Println("No cookie found! Starting new session.") + return makeSession(w, r) + } + var session SessionState + if err := secureCookie.Decode(cookieName, cookie.Value, &session); err != nil { + log.Printf("Invalid or corrupted session. Starting another: %s", err.Error()) + return makeSession(w, r) + } + + currentTime := time.Now() + if currentTime.Sub(session.SessionStart) > maxSessionLen { + log.Printf("Session starting at %s is expired. Starting another.", + session.SessionStart.Format(time.RFC822)) + return makeSession(w, r) + } + saveSession(&session, w, r) + return &session, nil +} + +// reportError serves the error page with the given message. +func reportError(w http.ResponseWriter, msg string, code int) { + errData := struct { + Code int + CodeString string + Message string + }{ + Code: code, + CodeString: http.StatusText(code), + Message: msg, + } + w.WriteHeader(code) + err := templates.ExecuteTemplate(w, "error.html", errData) + if err != nil { + log.Println("Failed to display error.html!!") + } +} + +// makeBugChomperPage builds and serves the BugChomper page. +func makeBugChomperPage(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r) + if err != nil { + reportError(w, err.Error(), http.StatusInternalServerError) + return + } + issueTracker := session.IssueTracker + user, err := issueTracker.GetLoggedInUser() + if err != nil { + reportError(w, err.Error(), http.StatusInternalServerError) + return + } + log.Println("Loading bugs for " + user) + bugList, err := issueTracker.GetBugs(project, user) + if err != nil { + reportError(w, err.Error(), http.StatusInternalServerError) + return + } + bugsById := make(map[string]*issue_tracker.Issue) + bugsByPriority := make(map[string][]*issue_tracker.Issue) + for _, bug := range bugList.Items { + bugsById[strconv.Itoa(bug.Id)] = bug + var bugPriority string + for _, label := range bug.Labels { + if strings.HasPrefix(label, priorityPrefix) { + bugPriority = label[len(priorityPrefix):] + } + } + if _, ok := bugsByPriority[bugPriority]; !ok { + bugsByPriority[bugPriority] = make( + []*issue_tracker.Issue, 0) + } + bugsByPriority[bugPriority] = append( + bugsByPriority[bugPriority], bug) + } + bugsJson, err := json.Marshal(bugsById) + if err != nil { + reportError(w, err.Error(), http.StatusInternalServerError) + return + } + data := struct { + Title string + User string + BugsJson template.JS + BugsByPriority *map[string][]*issue_tracker.Issue + Priorities []string + PriorityPrefix string + }{ + Title: "BugChomper", + User: user, + BugsJson: template.JS(string(bugsJson)), + BugsByPriority: &bugsByPriority, + Priorities: issue_tracker.BugPriorities, + PriorityPrefix: priorityPrefix, + } + + if err := templates.ExecuteTemplate(w, "bug_chomper.html", data); err != nil { + reportError(w, err.Error(), http.StatusInternalServerError) + return + } +} + +// authIfNeeded determines whether the current user is logged in. If not, it +// redirects to a login page. Returns true if the user is redirected and false +// otherwise. +func authIfNeeded(w http.ResponseWriter, r *http.Request) bool { + session, err := getSession(w, r) + if err != nil { + reportError(w, err.Error(), http.StatusInternalServerError) + return false + } + issueTracker := session.IssueTracker + if !issueTracker.IsAuthenticated() { + loginURL := issueTracker.MakeAuthRequestURL() + log.Println("Redirecting for login:", loginURL) + http.Redirect(w, r, loginURL, http.StatusTemporaryRedirect) + return true + } + return false +} + +// submitData attempts to submit data from a POST request to the IssueTracker. +func submitData(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r) + if err != nil { + reportError(w, err.Error(), http.StatusInternalServerError) + return + } + issueTracker := session.IssueTracker + edits := r.FormValue("all_edits") + var editsMap map[string]*issue_tracker.Issue + if err := json.Unmarshal([]byte(edits), &editsMap); err != nil { + errMsg := "Could not parse edits from form response: " + err.Error() + reportError(w, errMsg, http.StatusInternalServerError) + return + } + data := struct { + Title string + Message string + BackLink string + }{} + if len(editsMap) == 0 { + data.Title = "No Changes Submitted" + data.Message = "You didn't change anything!" + data.BackLink = "" + if err := templates.ExecuteTemplate(w, "submitted.html", data); err != nil { + reportError(w, err.Error(), http.StatusInternalServerError) + return + } + return + } + errorList := make([]error, 0) + for issueId, newIssue := range editsMap { + log.Println("Editing issue " + issueId) + if err := issueTracker.SubmitIssueChanges(newIssue, issueComment); err != nil { + errorList = append(errorList, err) + } + } + if len(errorList) > 0 { + errorStrings := "" + for _, err := range errorList { + errorStrings += err.Error() + "\n" + } + errMsg := "Not all changes could be submitted: \n" + errorStrings + reportError(w, errMsg, http.StatusInternalServerError) + return + } + data.Title = "Submitted Changes" + data.Message = "Your changes were submitted to the issue tracker." + data.BackLink = "" + if err := templates.ExecuteTemplate(w, "submitted.html", data); err != nil { + reportError(w, err.Error(), http.StatusInternalServerError) + return + } + return +} + +// handleBugChomper handles HTTP requests for the bug_chomper page. +func handleBugChomper(w http.ResponseWriter, r *http.Request) { + if authIfNeeded(w, r) { + return + } + switch r.Method { + case "GET": + makeBugChomperPage(w, r) + case "POST": + submitData(w, r) + } +} + +// handleOAuth2Callback handles callbacks from the OAuth2 sign-in. +func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r) + if err != nil { + reportError(w, err.Error(), http.StatusInternalServerError) + } + issueTracker := session.IssueTracker + invalidLogin := "Invalid login credentials" + params, err := url.ParseQuery(r.URL.RawQuery) + if err != nil { + reportError(w, invalidLogin+": "+err.Error(), http.StatusForbidden) + return + } + code, ok := params["code"] + if !ok { + reportError(w, invalidLogin+": redirect did not include auth code.", + http.StatusForbidden) + return + } + log.Println("Upgrading auth token:", code[0]) + if err := issueTracker.UpgradeCode(code[0]); err != nil { + errMsg := "failed to upgrade token: " + err.Error() + reportError(w, errMsg, http.StatusForbidden) + return + } + if err := saveSession(session, w, r); err != nil { + reportError(w, "failed to save session: "+err.Error(), + http.StatusInternalServerError) + return + } + http.Redirect(w, r, session.OrigRequestURL, http.StatusTemporaryRedirect) + return +} + +// handleRoot is the handler function for all HTTP requests at the root level. +func handleRoot(w http.ResponseWriter, r *http.Request) { + log.Println("Fetching " + r.URL.Path) + if r.URL.Path == "/" || r.URL.Path == "/index.html" { + handleBugChomper(w, r) + return + } + http.NotFound(w, r) +} + +// Run the BugChomper server. +func main() { + var public bool + flag.BoolVar( + &public, "public", false, "Make this server publicly accessible.") + flag.Parse() + + http.HandleFunc("/", handleRoot) + http.HandleFunc(oauthCallbackPath, handleOAuth2Callback) + http.Handle("/res/", http.FileServer(http.Dir(curdir))) + port := ":" + strconv.Itoa(defaultPort) + log.Println("Server is running at " + scheme + "://" + localHost + port) + var err error + if public { + log.Println("WARNING: This server is not secure and should not be made " + + "publicly accessible.") + scheme = "https" + err = http.ListenAndServeTLS(port, certFile, keyFile, nil) + } else { + scheme = "http" + err = http.ListenAndServe(localHost+port, nil) + } + if err != nil { + log.Println(err.Error()) + } +} diff --git a/chromium/third_party/skia/tools/bug_chomper/templates/bug_chomper.html b/chromium/third_party/skia/tools/bug_chomper/templates/bug_chomper.html new file mode 100644 index 00000000000..df08570b865 --- /dev/null +++ b/chromium/third_party/skia/tools/bug_chomper/templates/bug_chomper.html @@ -0,0 +1,118 @@ +<html> +<head> +<title>{{.Title}}</title> +<link rel="stylesheet" type="text/css" href="res/style.css" /> +<link rel="icon" type="image/ico" href="res/favicon.ico" /> +<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script> +<script type="text/javascript" src="res/third_party/jquery.tablednd.js"></script> +<script type="text/javascript"> +"use strict"; + +var issues = {{.BugsJson}}; +var edited = {}; + +function edit_label(bug_id, old_value, new_value) { + console.log("issue[" + bug_id + "]: " + old_value + " -> " + new_value); + if (!edited[bug_id]) { + edited[bug_id] = JSON.parse(JSON.stringify(issues[bug_id])); + } + var old_index = edited[bug_id]["labels"].indexOf(old_value); + if (old_index > -1) { + edited[bug_id]["labels"][old_index] = new_value; + } else { + edited[bug_id]["labels"].push(new_value) + } + if (JSON.stringify(issues[bug_id]) == JSON.stringify(edited[bug_id])) { + console.log("Not changing " + bug_id); + delete edited[bug_id] + } + document.getElementById("all_edits").value = JSON.stringify(edited); +} + +</script> +</head> +<body> +<h1>BugChomper</h1> + +<form method="post"> +<input type="hidden" name="all_edits" id="all_edits" value="{}" /> +<input type="submit" value="Submit changes to issue tracker" /> +</form> +<table id="buglist"> + <thead> + <tr id="table_header" class="nodrag tr_head"> + <td colspan=3><h2>Open bugs for {{.User}}</h2></td> + </tr> + <tr id="table_subheader" class="nodrag tr_head"> + <td>ID</td> + <td>Priority</td> + <td>Title</td> + </tr> + </thead> + <tbody> + {{with $all_data := .}} + {{range $index, $priority := index $all_data.Priorities}} + <tr id="priority_{{$priority}}" + class="{{if eq $index 0}}nodrop{{else}}{{end}} nodrag priority_row priority_{{$priority}}" + > + <td colspan=3 class="priority_td">Priority {{$priority}}</td> + </tr> + {{range $index, $bug := index $all_data.BugsByPriority $priority}} + <tr id="{{$bug.Id}}" class="priority_{{$priority}}"> + <td id="id_{{$bug.Id}}"> + <a href="{{$bug.URL}}" target="_blank">{{$bug.Id}}</a> + </td> + <td id="priority_{{$bug.Id}}">{{$priority}}</td> + <td id="title_{{$bug.Id}}">{{$bug.Title}}</td> + </tr> + {{end}} + {{end}} + {{end}} + </tbody> +</table> + +<script type="text/javascript"> +$(document).ready(function() { + $("#buglist").tableDnD({ + onDrop: function(table, dropped_row) { + var id = dropped_row.id; + var css_priority_prefix = "priority_" + var new_priority = null; + var dropped_index = null; + var thead_rows = table.tHead.rows; + var tbody_rows = table.tBodies[0].rows; + var all_rows = []; + for (var i = 0; i < thead_rows.length; i++) { + all_rows.push(thead_rows[i]); + } + for (var i = 0; i < tbody_rows.length; i++) { + all_rows.push(tbody_rows[i]); + } + for (var i = 0; i < all_rows.length; i++) { + if (all_rows[i].id) { + if (all_rows[i].id.indexOf(css_priority_prefix) == 0) { + new_priority = all_rows[i].id.substring(css_priority_prefix.length); + } + if (all_rows[i].id == id) { + break; + } + } else { + console.warn("No id for:"); + console.warn(all_rows[i]); + } + } + if (new_priority) { + priority_td = document.getElementById(css_priority_prefix + id); + old_priority = priority_td.innerHTML; + if (priority_td && new_priority != old_priority) { + priority_td.innerHTML = new_priority; + document.getElementById(id).className = css_priority_prefix + new_priority; + edit_label(id, "{{.PriorityPrefix}}" + old_priority, "{{.PriorityPrefix}}" + new_priority); + } + } + } + }); +}); +</script> +</body> +</html> diff --git a/chromium/third_party/skia/tools/bug_chomper/templates/error.html b/chromium/third_party/skia/tools/bug_chomper/templates/error.html new file mode 100644 index 00000000000..1e8fcda4917 --- /dev/null +++ b/chromium/third_party/skia/tools/bug_chomper/templates/error.html @@ -0,0 +1,12 @@ +<html> +<head> +<title>Error {{.Code}}: {{.CodeString}}</title> +<link rel="stylesheet" type="text/css" href="res/style.css" /> +<link rel="icon" type="image/ico" href="res/favicon.ico" /> +</head> +<body> +<h1>Error {{.Code}}: {{.CodeString}}</h1> +{{.Message}} +<br/> +</body> +</html> diff --git a/chromium/third_party/skia/tools/bug_chomper/templates/submitted.html b/chromium/third_party/skia/tools/bug_chomper/templates/submitted.html new file mode 100644 index 00000000000..2b09c238a81 --- /dev/null +++ b/chromium/third_party/skia/tools/bug_chomper/templates/submitted.html @@ -0,0 +1,13 @@ +<html> +<head> +<title>{{.Title}}</title> +<link rel="stylesheet" type="text/css" href="res/style.css" /> +<link rel="icon" type="image/ico" href="res/favicon.ico" /> +</head> +<body> +<h1>{{.Title}}</h1> +{{.Message}} +<br/> +<a href="{{.BackLink}}">Go back</a> +</body> +</html> diff --git a/chromium/third_party/skia/tools/buildbot_globals.py b/chromium/third_party/skia/tools/buildbot_globals.py new file mode 100755 index 00000000000..bb6c38fc841 --- /dev/null +++ b/chromium/third_party/skia/tools/buildbot_globals.py @@ -0,0 +1,76 @@ +#!/usr/bin/python + +# Copyright (c) 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Provides read access to buildbot's global_variables.json . +""" + + +import HTMLParser +import json +import re +import retrieve_from_googlesource +import svn +import sys + + +_global_vars = None + + +SKIABOT_REPO = 'https://skia.googlesource.com/buildbot' +_GLOBAL_VARS_PATH = 'site_config/global_variables.json' + + +class GlobalVarsRetrievalError(Exception): + """Exception which is raised when the global_variables.json file cannot be + retrieved from the Skia buildbot repository.""" + pass + + +class JsonDecodeError(Exception): + """Exception which is raised when the global_variables.json file cannot be + interpreted as JSON. This may be due to the file itself being incorrectly + formatted or due to an incomplete or corrupted downloaded version of the file. + """ + pass + + +class NoSuchGlobalVariable(KeyError): + """Exception which is raised when a given variable is not found in the + global_variables.json file.""" + pass + + +def Get(var_name): + """Return the value associated with this name in global_variables.json. + + Args: + var_name: string; the variable to look up. + Returns: + The value of the variable. + Raises: + NoSuchGlobalVariable if there is no variable with that name. + """ + global _global_vars + if not _global_vars: + try: + global_vars_text = retrieve_from_googlesource.get(SKIABOT_REPO, + _GLOBAL_VARS_PATH) + except Exception as e: + raise GlobalVarsRetrievalError('Failed to retrieve %s from %s:\n%s' % + (_GLOBAL_VARS_PATH, SKIABOT_REPO, str(e))) + try: + _global_vars = json.loads(global_vars_text) + except ValueError as e: + raise JsonDecodeError(e.message + '\n' + global_vars_text) + try: + return _global_vars[var_name]['value'] + except KeyError: + raise NoSuchGlobalVariable(var_name) + + +if __name__ == '__main__': + print Get(sys.argv[1]) diff --git a/chromium/third_party/skia/tools/chromium/chrome_changes b/chromium/third_party/skia/tools/chromium/chrome_changes new file mode 100644 index 00000000000..18f67e7520b --- /dev/null +++ b/chromium/third_party/skia/tools/chromium/chrome_changes @@ -0,0 +1,7 @@ +# This file contains references to Chrome CLs which are associated with Skia +# changes. If your Skia change requires a change in Chrome, add a line to this +# file which contains the codereview issue(s) for the Chrome change, eg: +# https://codereview.chromium.org/249493003/ +# In the future, these changes will be automatically applied and committed as +# part of the DEPS roll which contains the Skia change. +https://codereview.chromium.org/265513005/ diff --git a/chromium/third_party/skia/tools/compare_codereview.py b/chromium/third_party/skia/tools/compare_codereview.py new file mode 100755 index 00000000000..a58b3c697bf --- /dev/null +++ b/chromium/third_party/skia/tools/compare_codereview.py @@ -0,0 +1,414 @@ +#!/usr/bin/python2 + +# Copyright 2014 Google Inc. +# +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Skia's Chromium Codereview Comparison Script. + +This script takes two Codereview URLs, looks at the trybot results for +the two codereviews and compares the results. + +Usage: + compare_codereview.py CONTROL_URL ROLL_URL +""" + +import collections +import os +import re +import sys +import urllib2 +import HTMLParser + + +class CodeReviewHTMLParser(HTMLParser.HTMLParser): + """Parses CodeReview web page. + + Use the CodeReviewHTMLParser.parse static function to make use of + this class. + + This uses the HTMLParser class because it's the best thing in + Python's standard library. We need a little more power than a + regex. [Search for "You can't parse [X]HTML with regex." for more + information. + """ + # pylint: disable=I0011,R0904 + @staticmethod + def parse(url): + """Parses a CodeReview web pages. + + Args: + url (string), a codereview URL like this: + 'https://codereview.chromium.org/?????????'. + + Returns: + A dictionary; the keys are bot_name strings, the values + are CodeReviewHTMLParser.Status objects + """ + parser = CodeReviewHTMLParser() + try: + parser.feed(urllib2.urlopen(url).read()) + except (urllib2.URLError,): + print >> sys.stderr, 'Error getting', url + return None + parser.close() + return parser.statuses + + # namedtuples are like lightweight structs in Python. The low + # overhead of a tuple, but the ease of use of an object. + Status = collections.namedtuple('Status', ['status', 'url']) + + def __init__(self): + HTMLParser.HTMLParser.__init__(self) + self._id = None + self._status = None + self._href = None + self._anchor_data = '' + self._currently_parsing_trybotdiv = False + # statuses is a dictionary of CodeReviewHTMLParser.Status + self.statuses = {} + + def handle_starttag(self, tag, attrs): + """Overrides the HTMLParser method to implement functionality. + + [[begin standard library documentation]] + This method is called to handle the start of a tag + (e.g. <div id="main">). + + The tag argument is the name of the tag converted to lower + case. The attrs argument is a list of (name, value) pairs + containing the attributes found inside the tag's <> + brackets. The name will be translated to lower case, and + quotes in the value have been removed, and character and + entity references have been replaced. + + For instance, for the tag <A HREF="http://www.cwi.nl/">, this + method would be called as handle_starttag('a', [('href', + 'http://www.cwi.nl/')]). + [[end standard library documentation]] + """ + attrs = dict(attrs) + if tag == 'div': + # We are looking for <div id="tryjobdiv*">. + id_attr = attrs.get('id','') + if id_attr.startswith('tryjobdiv'): + self._id = id_attr + if (self._id and tag == 'a' + and 'build-result' in attrs.get('class', '').split()): + # If we are already inside a <div id="tryjobdiv*">, we + # look for a link if the form + # <a class="build-result" href="*">. Then we save the + # (non-standard) status attribute and the URL. + self._status = attrs.get('status') + self._href = attrs.get('href') + self._currently_parsing_trybotdiv = True + # Start saving anchor data. + + def handle_data(self, data): + """Overrides the HTMLParser method to implement functionality. + + [[begin standard library documentation]] + This method is called to process arbitrary data (e.g. text + nodes and the content of <script>...</script> and + <style>...</style>). + [[end standard library documentation]] + """ + # Save the text inside the <a></a> tags. Assume <a> tags + # aren't nested. + if self._currently_parsing_trybotdiv: + self._anchor_data += data + + def handle_endtag(self, tag): + """Overrides the HTMLParser method to implement functionality. + + [[begin standard library documentation]] + This method is called to handle the end tag of an element + (e.g. </div>). The tag argument is the name of the tag + converted to lower case. + [[end standard library documentation]] + """ + if tag == 'a' and self._status: + # We take the accumulated self._anchor_data and save it as + # the bot name. + bot = self._anchor_data.strip() + stat = CodeReviewHTMLParser.Status(status=self._status, + url=self._href) + if bot: + # Add to accumulating dictionary. + self.statuses[bot] = stat + # Reset state to search for the next bot. + self._currently_parsing_trybotdiv = False + self._anchor_data = '' + self._status = None + self._href = None + + +class BuilderHTMLParser(HTMLParser.HTMLParser): + """parses Trybot web pages. + + Use the BuilderHTMLParser.parse static function to make use of + this class. + + This uses the HTMLParser class because it's the best thing in + Python's standard library. We need a little more power than a + regex. [Search for "You can't parse [X]HTML with regex." for more + information. + """ + # pylint: disable=I0011,R0904 + @staticmethod + def parse(url): + """Parses a Trybot web page. + + Args: + url (string), a trybot result URL. + + Returns: + An array of BuilderHTMLParser.Results, each a description + of failure results, along with an optional url + """ + parser = BuilderHTMLParser() + try: + parser.feed(urllib2.urlopen(url).read()) + except (urllib2.URLError,): + print >> sys.stderr, 'Error getting', url + return [] + parser.close() + return parser.failure_results + + Result = collections.namedtuple('Result', ['text', 'url']) + + def __init__(self): + HTMLParser.HTMLParser.__init__(self) + self.failure_results = [] + self._current_failure_result = None + self._divlevel = None + self._li_level = 0 + self._li_data = '' + self._current_failure = False + self._failure_results_url = '' + + def handle_starttag(self, tag, attrs): + """Overrides the HTMLParser method to implement functionality. + + [[begin standard library documentation]] + This method is called to handle the start of a tag + (e.g. <div id="main">). + + The tag argument is the name of the tag converted to lower + case. The attrs argument is a list of (name, value) pairs + containing the attributes found inside the tag's <> + brackets. The name will be translated to lower case, and + quotes in the value have been removed, and character and + entity references have been replaced. + + For instance, for the tag <A HREF="http://www.cwi.nl/">, this + method would be called as handle_starttag('a', [('href', + 'http://www.cwi.nl/')]). + [[end standard library documentation]] + """ + attrs = dict(attrs) + if tag == 'li': + # <li> tags can be nested. So we have to count the + # nest-level for backing out. + self._li_level += 1 + return + if tag == 'div' and attrs.get('class') == 'failure result': + # We care about this sort of thing: + # <li> + # <li> + # <li> + # <div class="failure result">...</div> + # </li> + # </li> + # We want this text here. + # </li> + if self._li_level > 0: + self._current_failure = True # Tells us to keep text. + return + + if tag == 'a' and self._current_failure: + href = attrs.get('href') + # Sometimes we want to keep the stdio url. We always + # return it, just in case. + if href.endswith('/logs/stdio'): + self._failure_results_url = href + + def handle_data(self, data): + """Overrides the HTMLParser method to implement functionality. + + [[begin standard library documentation]] + This method is called to process arbitrary data (e.g. text + nodes and the content of <script>...</script> and + <style>...</style>). + [[end standard library documentation]] + """ + if self._current_failure: + self._li_data += data + + def handle_endtag(self, tag): + """Overrides the HTMLParser method to implement functionality. + + [[begin standard library documentation]] + This method is called to handle the end tag of an element + (e.g. </div>). The tag argument is the name of the tag + converted to lower case. + [[end standard library documentation]] + """ + if tag == 'li': + self._li_level -= 1 + if 0 == self._li_level: + if self._current_failure: + result = self._li_data.strip() + first = result.split()[0] + if first: + result = re.sub( + r'^%s(\s+%s)+' % (first, first), first, result) + # Sometimes, it repeats the same thing + # multiple times. + result = re.sub(r'unexpected flaky.*', '', result) + # Remove some extra unnecessary text. + result = re.sub(r'\bpreamble\b', '', result) + result = re.sub(r'\bstdio\b', '', result) + url = self._failure_results_url + self.failure_results.append( + BuilderHTMLParser.Result(result, url)) + self._current_failure_result = None + # Reset the state. + self._current_failure = False + self._li_data = '' + self._failure_results_url = '' + + +def printer(indent, string): + """Print indented, wrapped text. + """ + def wrap_to(line, columns): + """Wrap a line to the given number of columns, return a list + of strings. + """ + ret = [] + nextline = '' + for word in line.split(): + if nextline: + if len(nextline) + 1 + len(word) > columns: + ret.append(nextline) + nextline = word + else: + nextline += (' ' + word) + else: + nextline = word + if nextline: + ret.append(nextline) + return ret + out = sys.stdout + spacer = ' ' + for line in string.split('\n'): + for i, wrapped_line in enumerate(wrap_to(line, 68 - (2 * indent))): + out.write(spacer * indent) + if i > 0: + out.write(spacer) + out.write(wrapped_line) + out.write('\n') + out.flush() + + +def main(control_url, roll_url, verbosity=1): + """Compare two Codereview URLs + + Args: + control_url, roll_url: (strings) URL of the format + https://codereview.chromium.org/????????? + + verbosity: (int) verbose level. 0, 1, or 2. + """ + # pylint: disable=I0011,R0914,R0912 + control = CodeReviewHTMLParser.parse(control_url) + roll = CodeReviewHTMLParser.parse(roll_url) + all_bots = set(control) & set(roll) # Set intersection. + if not all_bots: + print >> sys.stderr, ( + 'Error: control %s and roll %s have no common trybots.' + % (list(control), list(roll))) + return + + control_name = '[control %s]' % control_url.split('/')[-1] + roll_name = '[roll %s]' % roll_url.split('/')[-1] + + out = sys.stdout + + for bot in sorted(all_bots): + if (roll[bot].status == 'success'): + if verbosity > 1: + printer(0, '==%s==' % bot) + printer(1, 'OK') + continue + + if control[bot].status != 'failure' and roll[bot].status != 'failure': + continue + printer(0, '==%s==' % bot) + + formatted_results = [] + for (status, name, url) in [ + (control[bot].status, control_name, control[bot].url), + ( roll[bot].status, roll_name, roll[bot].url)]: + lines = [] + if status == 'failure': + results = BuilderHTMLParser.parse(url) + for result in results: + formatted_result = re.sub(r'(\S*\.html) ', '\n__\g<1>\n', result.text) + # Strip runtimes. + formatted_result = re.sub(r'\(.*\)', '', formatted_result) + lines.append((2, formatted_result)) + if ('compile' in result.text or '...and more' in result.text): + lines.append((3, re.sub('/[^/]*$', '/', url) + result.url)) + formatted_results.append(lines) + + identical = formatted_results[0] == formatted_results[1] + + + for (formatted_result, (status, name, url)) in zip( + formatted_results, + [(control[bot].status, control_name, control[bot].url), + (roll[bot].status, roll_name, roll[bot].url)]): + if status != 'failure' and not identical: + printer(1, name) + printer(2, status) + elif status == 'failure': + if identical: + printer(1, control_name + ' and ' + roll_name + ' failed identically') + else: + printer(1, name) + for (indent, line) in formatted_result: + printer(indent, line) + if identical: + break + out.write('\n') + + if verbosity > 0: + # Print out summary of all of the bots. + out.write('%11s %11s %4s %s\n\n' % + ('CONTROL', 'ROLL', 'DIFF', 'BOT')) + for bot in sorted(all_bots): + if roll[bot].status == 'success': + diff = '' + elif (control[bot].status == 'success' and + roll[bot].status == 'failure'): + diff = '!!!!' + elif ('pending' in control[bot].status or + 'pending' in roll[bot].status): + diff = '....' + else: + diff = '****' + out.write('%11s %11s %4s %s\n' % ( + control[bot].status, roll[bot].status, diff, bot)) + out.write('\n') + out.flush() + +if __name__ == '__main__': + if len(sys.argv) < 3: + print >> sys.stderr, __doc__ + exit(1) + main(sys.argv[1], sys.argv[2], + int(os.environ.get('COMPARE_CODEREVIEW_VERBOSITY', 1))) + diff --git a/chromium/third_party/skia/tools/copyright/fileparser.py b/chromium/third_party/skia/tools/copyright/fileparser.py new file mode 100644 index 00000000000..a4051e25b9a --- /dev/null +++ b/chromium/third_party/skia/tools/copyright/fileparser.py @@ -0,0 +1,92 @@ +''' +Copyright 2011 Google Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +''' + +import datetime +import re + +def CreateParser(filepath): + """Returns a Parser as appropriate for the file at this filepath. + """ + if (filepath.endswith('.cpp') or + filepath.endswith('.h') or + filepath.endswith('.c')): + return CParser() + else: + return None + + +class Parser(object): + """Base class for all language-specific parsers. + """ + + def __init__(self): + self._copyright_pattern = re.compile('copyright', re.IGNORECASE) + self._attribute_pattern = re.compile( + 'copyright.*\D(\d{4})\W*(\w.*[\w.])', re.IGNORECASE) + + def FindCopyrightBlock(self, comment_blocks): + """Given a list of comment block strings, return the one that seems + like the most likely copyright block. + + Returns None if comment_blocks was empty, or if we couldn't find + a comment block that contains copyright info.""" + if not comment_blocks: + return None + for block in comment_blocks: + if self._copyright_pattern.search(block): + return block + + def GetCopyrightBlockAttributes(self, comment_block): + """Given a comment block, return a tuple of attributes: (year, holder). + + If comment_block is None, or none of the attributes are found, + this will return (None, None).""" + if not comment_block: + return (None, None) + matches = self._attribute_pattern.findall(comment_block) + if not matches: + return (None, None) + first_match = matches[0] + return (first_match[0], first_match[1]) + + +class CParser(Parser): + """Parser that knows how to parse C/C++ files. + """ + + DEFAULT_YEAR = datetime.date.today().year + DEFAULT_HOLDER = 'Google Inc.' + COPYRIGHT_BLOCK_FORMAT = ''' +/* + * Copyright %s %s + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +''' + + def __init__(self): + super(CParser, self).__init__() + self._comment_pattern = re.compile('/\*.*?\*/', re.DOTALL) + + def FindAllCommentBlocks(self, file_contents): + """Returns a list of all comment blocks within these file contents. + """ + return self._comment_pattern.findall(file_contents) + + def CreateCopyrightBlock(self, year, holder): + """Returns a copyright block suitable for this language, with the + given attributes. + + @param year year in which to hold copyright (defaults to DEFAULT_YEAR) + @param holder holder of copyright (defaults to DEFAULT_HOLDER) + """ + if not year: + year = self.DEFAULT_YEAR + if not holder: + holder = self.DEFAULT_HOLDER + return self.COPYRIGHT_BLOCK_FORMAT % (year, holder) diff --git a/chromium/third_party/skia/tools/copyright/main.py b/chromium/third_party/skia/tools/copyright/main.py new file mode 100644 index 00000000000..24969a7ac54 --- /dev/null +++ b/chromium/third_party/skia/tools/copyright/main.py @@ -0,0 +1,107 @@ +''' +Copyright 2011 Google Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +''' + +''' +Updates all copyright headers within our code: +- For files that already have a copyright header, the header is modified + while keeping the year and holder intact. +- For files that don't have a copyright header, we add one with the current + year and default holder. + +@author: epoger@google.com +''' + +import os +import sys + +import fileparser + +# Only modify copyright stanzas if the copyright holder is one of these. +ALLOWED_COPYRIGHT_HOLDERS = [ + 'Google Inc.', + 'Skia', + 'The Android Open Source Project', +] + +def Main(root_directory): + """Run everything. + + @param root_directory root directory within which to modify all files + """ + filepaths = GetAllFilepaths(root_directory) + for filepath in filepaths: + parser = fileparser.CreateParser(filepath) + if not parser: + ReportWarning('cannot find a parser for file %s, skipping...' % + filepath) + continue + old_file_contents = ReadFileIntoString(filepath) + comment_blocks = parser.FindAllCommentBlocks(old_file_contents) + if not comment_blocks: + ReportWarning('cannot find any comment blocks in file %s' % + filepath) + old_copyright_block = parser.FindCopyrightBlock(comment_blocks) + if not old_copyright_block: + ReportWarning('cannot find copyright block in file %s' % filepath) + (year, holder) = parser.GetCopyrightBlockAttributes(old_copyright_block) + if holder and not ConfirmAllowedCopyrightHolder(holder): + ReportWarning( + 'unrecognized copyright holder "%s" in file %s, skipping...' % ( + holder, filepath)) + continue + new_copyright_block = parser.CreateCopyrightBlock(year, holder) + if old_copyright_block: + new_file_contents = old_file_contents.replace( + old_copyright_block, new_copyright_block, 1) + else: + new_file_contents = new_copyright_block + old_file_contents + WriteStringToFile(new_file_contents, filepath) + +def GetAllFilepaths(root_directory): + """Return a list of all files (absolute path for each one) within a tree. + + @param root_directory root directory within which to find all files + """ + path_list = [] + for dirpath, dirnames, filenames in os.walk(root_directory): + for filename in filenames: + path_list.append(os.path.abspath(os.path.join(dirpath, filename))) + return path_list + +def ReportWarning(text): + """Report a warning, but continue. + """ + print 'warning: %s' % text + +def ReportError(text): + """Report an error and raise an exception. + """ + raise IOError(text) + +def ReadFileIntoString(filepath): + """Returns the full contents of this file as a string. + """ + with open(filepath, 'r') as file_handle: + contents = file_handle.read() + return contents + +def WriteStringToFile(string, filepath): + """Writes this string out to filepath, replacing the file if it already + exists. + """ + with open(filepath, 'w') as file_handle: + file_handle.write(string) + +def ConfirmAllowedCopyrightHolder(holder): + """Returns True if this is one of our allowed copyright holders. + + @param holder copyright holder as a string + """ + return holder in ALLOWED_COPYRIGHT_HOLDERS + + +Main(sys.argv[1]) diff --git a/chromium/third_party/skia/tools/coverage.sh b/chromium/third_party/skia/tools/coverage.sh new file mode 100755 index 00000000000..8fe75c5cd5e --- /dev/null +++ b/chromium/third_party/skia/tools/coverage.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +# Run from Skia trunk something like this: +# $ tools/coverage.sh tests +# or +# $ tools/coverage.sh gm + +set -x +set -e + +COMMAND=$@ +GCOV=$(realpath tools/gcov_shim) + +QUIET=-q + +# Build all of Skia. +./gyp_skia +ninja -C out/Coverage + +# Generate a zero-baseline so files not covered by $COMMAND will still show up in the report. +# This reads the .gcno files that are created at compile time. +lcov $QUIET --gcov-tool=$GCOV -c -b out/Coverage -d out/Coverage -o /tmp/baseline -i + +# Running the binary generates the real coverage information, the .gcda files. +out/Coverage/$COMMAND +lcov $QUIET --gcov-tool=$GCOV -c -b out/Coverage -d out/Coverage -o /tmp/coverage + +lcov $QUIET -a /tmp/baseline -a /tmp/coverage -o /tmp/merged + +genhtml $QUIET /tmp/merged --legend -o out/Coverage/report +xdg-open out/Coverage/report/index.html diff --git a/chromium/third_party/skia/tools/doxygen_footer.txt b/chromium/third_party/skia/tools/doxygen_footer.txt new file mode 100644 index 00000000000..51cee383ebc --- /dev/null +++ b/chromium/third_party/skia/tools/doxygen_footer.txt @@ -0,0 +1,9 @@ +<!-- + This is the static footer that Doxygen appends to every page. + It is not properly formed html; it must end with unbalanced /body + and /html tags. +--> +<hr class="footer"/> +<iframe src="../iframe_footer.html" width="100%" frameborder=0></iframe> +</body> +</html> diff --git a/chromium/third_party/skia/tools/dump_record.cpp b/chromium/third_party/skia/tools/dump_record.cpp new file mode 100644 index 00000000000..4fb1cf502f8 --- /dev/null +++ b/chromium/third_party/skia/tools/dump_record.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <stdio.h> + +#include "SkCommandLineFlags.h" +#include "SkGraphics.h" +#include "SkPicture.h" +#include "SkRecordOpts.h" +#include "SkRecorder.h" +#include "SkStream.h" + +#include "DumpRecord.h" +#include "LazyDecodeBitmap.h" + +DEFINE_string2(skps, r, "", ".SKPs to dump."); +DEFINE_string(match, "", "The usual filters on file names to dump."); +DEFINE_bool2(optimize, O, false, "Run SkRecordOptimize before dumping."); +DEFINE_int32(tile, 1000000000, "Simulated tile size."); +DEFINE_bool(timeWithCommand, false, "If true, print time next to command, else in first column."); + +static void dump(const char* name, int w, int h, const SkRecord& record) { + SkBitmap bitmap; + bitmap.allocN32Pixels(w, h); + SkCanvas canvas(bitmap); + canvas.clipRect(SkRect::MakeWH(SkIntToScalar(FLAGS_tile), + SkIntToScalar(FLAGS_tile))); + + printf("%s %s\n", FLAGS_optimize ? "optimized" : "not-optimized", name); + + DumpRecord(record, &canvas, FLAGS_timeWithCommand); +} + + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + SkCommandLineFlags::Parse(argc, argv); + SkAutoGraphics ag; + + for (int i = 0; i < FLAGS_skps.count(); i++) { + if (SkCommandLineFlags::ShouldSkip(FLAGS_match, FLAGS_skps[i])) { + continue; + } + + SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(FLAGS_skps[i])); + if (!stream) { + SkDebugf("Could not read %s.\n", FLAGS_skps[i]); + exit(1); + } + SkAutoTUnref<SkPicture> src( + SkPicture::CreateFromStream(stream, sk_tools::LazyDecodeBitmap)); + if (!src) { + SkDebugf("Could not read %s as an SkPicture.\n", FLAGS_skps[i]); + exit(1); + } + const int w = src->width(), h = src->height(); + + SkRecord record; + SkRecorder canvas(&record, w, h); + src->draw(&canvas); + + if (FLAGS_optimize) { + SkRecordOptimize(&record); + } + + dump(FLAGS_skps[i], w, h, record); + } + + return 0; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/filtermain.cpp b/chromium/third_party/skia/tools/filtermain.cpp new file mode 100644 index 00000000000..2a404f92cf1 --- /dev/null +++ b/chromium/third_party/skia/tools/filtermain.cpp @@ -0,0 +1,847 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkDebugCanvas.h" +#include "SkDevice.h" +#include "SkForceLinking.h" +#include "SkGraphics.h" +#include "SkImageDecoder.h" +#include "SkImageEncoder.h" +#include "SkOSFile.h" +#include "SkPicture.h" +#include "SkPicturePlayback.h" +#include "SkPictureRecord.h" +#include "SkPictureRecorder.h" +#include "SkStream.h" +#include "picture_utils.h" + +__SK_FORCE_IMAGE_DECODER_LINKING; + +static void usage() { + SkDebugf("Usage: filter -i inFile [-o outFile] [--input-dir path] [--output-dir path]\n"); + SkDebugf(" [-h|--help]\n\n"); + SkDebugf(" -i inFile : file to filter.\n"); + SkDebugf(" -o outFile : result of filtering.\n"); + SkDebugf(" --input-dir : process all files in dir with .skp extension.\n"); + SkDebugf(" --output-dir : results of filtering the input dir.\n"); + SkDebugf(" -h|--help : Show this help message.\n"); +} + +// Is the supplied paint simply a color? +static bool is_simple(const SkPaint& p) { + return NULL == p.getPathEffect() && + NULL == p.getShader() && + NULL == p.getXfermode() && + NULL == p.getMaskFilter() && + NULL == p.getColorFilter() && + NULL == p.getRasterizer() && + NULL == p.getLooper() && + NULL == p.getImageFilter(); +} + + +// Check for: +// SAVE_LAYER +// DRAW_BITMAP_RECT_TO_RECT +// RESTORE +// where the saveLayer's color can be moved into the drawBitmapRect +static bool check_0(SkDebugCanvas* canvas, int curCommand) { + if (SAVE_LAYER != canvas->getDrawCommandAt(curCommand)->getType() || + canvas->getSize() <= curCommand+2 || + DRAW_BITMAP_RECT_TO_RECT != canvas->getDrawCommandAt(curCommand+1)->getType() || + RESTORE != canvas->getDrawCommandAt(curCommand+2)->getType()) { + return false; + } + + SkSaveLayerCommand* saveLayer = + (SkSaveLayerCommand*) canvas->getDrawCommandAt(curCommand); + SkDrawBitmapRectCommand* dbmr = + (SkDrawBitmapRectCommand*) canvas->getDrawCommandAt(curCommand+1); + + const SkPaint* saveLayerPaint = saveLayer->paint(); + SkPaint* dbmrPaint = dbmr->paint(); + + // For this optimization we only fold the saveLayer and drawBitmapRect + // together if the saveLayer's draw is simple (i.e., no fancy effects) + // and the only difference in the colors is their alpha value + SkColor layerColor = saveLayerPaint->getColor() | 0xFF000000; // force opaque + SkColor dbmrColor = dbmrPaint->getColor() | 0xFF000000; // force opaque + + // If either operation lacks a paint then the collapse is trivial + return NULL == saveLayerPaint || + NULL == dbmrPaint || + (is_simple(*saveLayerPaint) && dbmrColor == layerColor); +} + +// Fold the saveLayer's alpha into the drawBitmapRect and remove the saveLayer +// and restore +static void apply_0(SkDebugCanvas* canvas, int curCommand) { + SkSaveLayerCommand* saveLayer = + (SkSaveLayerCommand*) canvas->getDrawCommandAt(curCommand); + const SkPaint* saveLayerPaint = saveLayer->paint(); + + // if (NULL == saveLayerPaint) the dbmr's paint doesn't need to be changed + if (NULL != saveLayerPaint) { + SkDrawBitmapRectCommand* dbmr = + (SkDrawBitmapRectCommand*) canvas->getDrawCommandAt(curCommand+1); + SkPaint* dbmrPaint = dbmr->paint(); + + if (NULL == dbmrPaint) { + // if the DBMR doesn't have a paint just use the saveLayer's + dbmr->setPaint(*saveLayerPaint); + } else if (NULL != saveLayerPaint) { + // Both paints are present so their alphas need to be combined + SkColor color = saveLayerPaint->getColor(); + int a0 = SkColorGetA(color); + + color = dbmrPaint->getColor(); + int a1 = SkColorGetA(color); + + int newA = SkMulDiv255Round(a0, a1); + SkASSERT(newA <= 0xFF); + + SkColor newColor = SkColorSetA(color, newA); + dbmrPaint->setColor(newColor); + } + } + + canvas->deleteDrawCommandAt(curCommand+2); // restore + canvas->deleteDrawCommandAt(curCommand); // saveLayer +} + +// Check for: +// SAVE_LAYER +// SAVE +// CLIP_RECT +// DRAW_BITMAP_RECT_TO_RECT +// RESTORE +// RESTORE +// where the saveLayer's color can be moved into the drawBitmapRect +static bool check_1(SkDebugCanvas* canvas, int curCommand) { + if (SAVE_LAYER != canvas->getDrawCommandAt(curCommand)->getType() || + canvas->getSize() <= curCommand+5 || + SAVE != canvas->getDrawCommandAt(curCommand+1)->getType() || + CLIP_RECT != canvas->getDrawCommandAt(curCommand+2)->getType() || + DRAW_BITMAP_RECT_TO_RECT != canvas->getDrawCommandAt(curCommand+3)->getType() || + RESTORE != canvas->getDrawCommandAt(curCommand+4)->getType() || + RESTORE != canvas->getDrawCommandAt(curCommand+5)->getType()) { + return false; + } + + SkSaveLayerCommand* saveLayer = + (SkSaveLayerCommand*) canvas->getDrawCommandAt(curCommand); + SkDrawBitmapRectCommand* dbmr = + (SkDrawBitmapRectCommand*) canvas->getDrawCommandAt(curCommand+3); + + const SkPaint* saveLayerPaint = saveLayer->paint(); + SkPaint* dbmrPaint = dbmr->paint(); + + // For this optimization we only fold the saveLayer and drawBitmapRect + // together if the saveLayer's draw is simple (i.e., no fancy effects) and + // and the only difference in the colors is that the saveLayer's can have + // an alpha while the drawBitmapRect's is opaque. + // TODO: it should be possible to fold them together even if they both + // have different non-255 alphas but this is low priority since we have + // never seen that case + // If either operation lacks a paint then the collapse is trivial + SkColor layerColor = saveLayerPaint->getColor() | 0xFF000000; // force opaque + + return NULL == saveLayerPaint || + NULL == dbmrPaint || + (is_simple(*saveLayerPaint) && dbmrPaint->getColor() == layerColor); +} + +// Fold the saveLayer's alpha into the drawBitmapRect and remove the saveLayer +// and restore +static void apply_1(SkDebugCanvas* canvas, int curCommand) { + SkSaveLayerCommand* saveLayer = + (SkSaveLayerCommand*) canvas->getDrawCommandAt(curCommand); + const SkPaint* saveLayerPaint = saveLayer->paint(); + + // if (NULL == saveLayerPaint) the dbmr's paint doesn't need to be changed + if (NULL != saveLayerPaint) { + SkDrawBitmapRectCommand* dbmr = + (SkDrawBitmapRectCommand*) canvas->getDrawCommandAt(curCommand+3); + SkPaint* dbmrPaint = dbmr->paint(); + + if (NULL == dbmrPaint) { + dbmr->setPaint(*saveLayerPaint); + } else { + SkColor newColor = SkColorSetA(dbmrPaint->getColor(), + SkColorGetA(saveLayerPaint->getColor())); + dbmrPaint->setColor(newColor); + } + } + + canvas->deleteDrawCommandAt(curCommand+5); // restore + canvas->deleteDrawCommandAt(curCommand); // saveLayer +} + +// Check for: +// SAVE +// CLIP_RECT +// DRAW_RECT +// RESTORE +// where the rect is entirely within the clip and the clip is an intersect +static bool check_2(SkDebugCanvas* canvas, int curCommand) { + if (SAVE != canvas->getDrawCommandAt(curCommand)->getType() || + canvas->getSize() <= curCommand+4 || + CLIP_RECT != canvas->getDrawCommandAt(curCommand+1)->getType() || + DRAW_RECT != canvas->getDrawCommandAt(curCommand+2)->getType() || + RESTORE != canvas->getDrawCommandAt(curCommand+3)->getType()) { + return false; + } + + SkClipRectCommand* cr = + (SkClipRectCommand*) canvas->getDrawCommandAt(curCommand+1); + SkDrawRectCommand* dr = + (SkDrawRectCommand*) canvas->getDrawCommandAt(curCommand+2); + + if (SkRegion::kIntersect_Op != cr->op()) { + return false; + } + + return cr->rect().contains(dr->rect()); +} + +// Remove everything but the drawRect +static void apply_2(SkDebugCanvas* canvas, int curCommand) { + canvas->deleteDrawCommandAt(curCommand+3); // restore + // drawRect + canvas->deleteDrawCommandAt(curCommand+1); // clipRect + canvas->deleteDrawCommandAt(curCommand); // save +} + +// Check for: +// SAVE +// CLIP_RRECT +// DRAW_RECT +// RESTORE +// where the rect entirely encloses the clip +static bool check_3(SkDebugCanvas* canvas, int curCommand) { + if (SAVE != canvas->getDrawCommandAt(curCommand)->getType() || + canvas->getSize() <= curCommand+4 || + CLIP_RRECT != canvas->getDrawCommandAt(curCommand+1)->getType() || + DRAW_RECT != canvas->getDrawCommandAt(curCommand+2)->getType() || + RESTORE != canvas->getDrawCommandAt(curCommand+3)->getType()) { + return false; + } + + SkClipRRectCommand* crr = + (SkClipRRectCommand*) canvas->getDrawCommandAt(curCommand+1); + SkDrawRectCommand* dr = + (SkDrawRectCommand*) canvas->getDrawCommandAt(curCommand+2); + + if (SkRegion::kIntersect_Op != crr->op()) { + return false; + } + + return dr->rect().contains(crr->rrect().rect()); +} + +// Replace everything with a drawRRect with the paint from the drawRect +// and the AA settings from the clipRRect +static void apply_3(SkDebugCanvas* canvas, int curCommand) { + + canvas->deleteDrawCommandAt(curCommand+3); // restore + + SkClipRRectCommand* crr = + (SkClipRRectCommand*) canvas->getDrawCommandAt(curCommand+1); + SkDrawRectCommand* dr = + (SkDrawRectCommand*) canvas->getDrawCommandAt(curCommand+2); + + // TODO: could skip paint re-creation if the AA settings already match + SkPaint newPaint = dr->paint(); + newPaint.setAntiAlias(crr->doAA()); + SkDrawRRectCommand* drr = new SkDrawRRectCommand(crr->rrect(), newPaint); + canvas->setDrawCommandAt(curCommand+2, drr); + + canvas->deleteDrawCommandAt(curCommand+1); // clipRRect + canvas->deleteDrawCommandAt(curCommand); // save +} + +// Check for: +// SAVE +// CLIP_RECT +// DRAW_BITMAP_RECT_TO_RECT +// RESTORE +// where the rect and drawBitmapRect dst exactly match +static bool check_4(SkDebugCanvas* canvas, int curCommand) { + if (SAVE != canvas->getDrawCommandAt(curCommand)->getType() || + canvas->getSize() <= curCommand+4 || + CLIP_RECT != canvas->getDrawCommandAt(curCommand+1)->getType() || + DRAW_BITMAP_RECT_TO_RECT != canvas->getDrawCommandAt(curCommand+2)->getType() || + RESTORE != canvas->getDrawCommandAt(curCommand+3)->getType()) { + return false; + } + + SkClipRectCommand* cr = + (SkClipRectCommand*) canvas->getDrawCommandAt(curCommand+1); + SkDrawBitmapRectCommand* dbmr = + (SkDrawBitmapRectCommand*) canvas->getDrawCommandAt(curCommand+2); + + if (SkRegion::kIntersect_Op != cr->op()) { + return false; + } + + return dbmr->dstRect() == cr->rect(); +} + +// Remove everything but the drawBitmapRect +static void apply_4(SkDebugCanvas* canvas, int curCommand) { + canvas->deleteDrawCommandAt(curCommand+3); // restore + // drawBitmapRectToRect + canvas->deleteDrawCommandAt(curCommand+1); // clipRect + canvas->deleteDrawCommandAt(curCommand); // save +} + +// Check for: +// TRANSLATE +// where the translate is zero +static bool check_5(SkDebugCanvas* canvas, int curCommand) { + if (TRANSLATE != canvas->getDrawCommandAt(curCommand)->getType()) { + return false; + } + + SkTranslateCommand* t = + (SkTranslateCommand*) canvas->getDrawCommandAt(curCommand); + + return 0 == t->x() && 0 == t->y(); +} + +// Just remove the translate +static void apply_5(SkDebugCanvas* canvas, int curCommand) { + canvas->deleteDrawCommandAt(curCommand); // translate +} + +// Check for: +// SCALE +// where the scale is 1,1 +static bool check_6(SkDebugCanvas* canvas, int curCommand) { + if (SCALE != canvas->getDrawCommandAt(curCommand)->getType()) { + return false; + } + + SkScaleCommand* s = (SkScaleCommand*) canvas->getDrawCommandAt(curCommand); + + return SK_Scalar1 == s->x() && SK_Scalar1 == s->y(); +} + +// Just remove the scale +static void apply_6(SkDebugCanvas* canvas, int curCommand) { + canvas->deleteDrawCommandAt(curCommand); // scale +} + +// Check for: +// SAVE +// CLIP_RECT +// SAVE_LAYER +// SAVE +// CLIP_RECT +// SAVE_LAYER +// SAVE +// CLIP_RECT +// DRAWBITMAPRECTTORECT +// RESTORE +// RESTORE +// RESTORE +// RESTORE +// RESTORE +// where: +// all the clipRect's are BW, nested, intersections +// the drawBitmapRectToRect is a 1-1 copy from src to dest +// the last (smallest) clip rect is a subset of the drawBitmapRectToRect's dest rect +// all the saveLayer's paints can be rolled into the drawBitmapRectToRect's paint +// This pattern is used by Google spreadsheet when drawing the toolbar buttons +static bool check_7(SkDebugCanvas* canvas, int curCommand) { + if (SAVE != canvas->getDrawCommandAt(curCommand)->getType() || + canvas->getSize() <= curCommand+13 || + CLIP_RECT != canvas->getDrawCommandAt(curCommand+1)->getType() || + SAVE_LAYER != canvas->getDrawCommandAt(curCommand+2)->getType() || + SAVE != canvas->getDrawCommandAt(curCommand+3)->getType() || + CLIP_RECT != canvas->getDrawCommandAt(curCommand+4)->getType() || + SAVE_LAYER != canvas->getDrawCommandAt(curCommand+5)->getType() || + SAVE != canvas->getDrawCommandAt(curCommand+6)->getType() || + CLIP_RECT != canvas->getDrawCommandAt(curCommand+7)->getType() || + DRAW_BITMAP_RECT_TO_RECT != canvas->getDrawCommandAt(curCommand+8)->getType() || + RESTORE != canvas->getDrawCommandAt(curCommand+9)->getType() || + RESTORE != canvas->getDrawCommandAt(curCommand+10)->getType() || + RESTORE != canvas->getDrawCommandAt(curCommand+11)->getType() || + RESTORE != canvas->getDrawCommandAt(curCommand+12)->getType() || + RESTORE != canvas->getDrawCommandAt(curCommand+13)->getType()) { + return false; + } + + SkClipRectCommand* clip0 = + (SkClipRectCommand*) canvas->getDrawCommandAt(curCommand+1); + SkSaveLayerCommand* saveLayer0 = + (SkSaveLayerCommand*) canvas->getDrawCommandAt(curCommand+2); + SkClipRectCommand* clip1 = + (SkClipRectCommand*) canvas->getDrawCommandAt(curCommand+4); + SkSaveLayerCommand* saveLayer1 = + (SkSaveLayerCommand*) canvas->getDrawCommandAt(curCommand+5); + SkClipRectCommand* clip2 = + (SkClipRectCommand*) canvas->getDrawCommandAt(curCommand+7); + SkDrawBitmapRectCommand* dbmr = + (SkDrawBitmapRectCommand*) canvas->getDrawCommandAt(curCommand+8); + + if (clip0->doAA() || clip1->doAA() || clip2->doAA()) { + return false; + } + + if (SkRegion::kIntersect_Op != clip0->op() || + SkRegion::kIntersect_Op != clip1->op() || + SkRegion::kIntersect_Op != clip2->op()) { + return false; + } + + if (!clip0->rect().contains(clip1->rect()) || + !clip1->rect().contains(clip2->rect())) { + return false; + } + + // The src->dest mapping needs to be 1-to-1 + if (NULL == dbmr->srcRect()) { + if (dbmr->bitmap().width() != dbmr->dstRect().width() || + dbmr->bitmap().height() != dbmr->dstRect().height()) { + return false; + } + } else { + if (dbmr->srcRect()->width() != dbmr->dstRect().width() || + dbmr->srcRect()->height() != dbmr->dstRect().height()) { + return false; + } + } + + if (!dbmr->dstRect().contains(clip2->rect())) { + return false; + } + + const SkPaint* saveLayerPaint0 = saveLayer0->paint(); + const SkPaint* saveLayerPaint1 = saveLayer1->paint(); + + if ((NULL != saveLayerPaint0 && !is_simple(*saveLayerPaint0)) || + (NULL != saveLayerPaint1 && !is_simple(*saveLayerPaint1))) { + return false; + } + + SkPaint* dbmrPaint = dbmr->paint(); + + if (NULL == dbmrPaint) { + return true; + } + + if (NULL != saveLayerPaint0) { + SkColor layerColor0 = saveLayerPaint0->getColor() | 0xFF000000; // force opaque + if (dbmrPaint->getColor() != layerColor0) { + return false; + } + } + + if (NULL != saveLayerPaint1) { + SkColor layerColor1 = saveLayerPaint1->getColor() | 0xFF000000; // force opaque + if (dbmrPaint->getColor() != layerColor1) { + return false; + } + } + + return true; +} + +// Reduce to a single drawBitmapRectToRect call by folding the clipRect's into +// the src and dst Rects and the saveLayer paints into the drawBitmapRectToRect's +// paint. +static void apply_7(SkDebugCanvas* canvas, int curCommand) { + SkSaveLayerCommand* saveLayer0 = + (SkSaveLayerCommand*) canvas->getDrawCommandAt(curCommand+2); + SkSaveLayerCommand* saveLayer1 = + (SkSaveLayerCommand*) canvas->getDrawCommandAt(curCommand+5); + SkClipRectCommand* clip2 = + (SkClipRectCommand*) canvas->getDrawCommandAt(curCommand+7); + SkDrawBitmapRectCommand* dbmr = + (SkDrawBitmapRectCommand*) canvas->getDrawCommandAt(curCommand+8); + + SkScalar newSrcLeft = dbmr->srcRect()->fLeft + clip2->rect().fLeft - dbmr->dstRect().fLeft; + SkScalar newSrcTop = dbmr->srcRect()->fTop + clip2->rect().fTop - dbmr->dstRect().fTop; + + SkRect newSrc = SkRect::MakeXYWH(newSrcLeft, newSrcTop, + clip2->rect().width(), clip2->rect().height()); + + dbmr->setSrcRect(newSrc); + dbmr->setDstRect(clip2->rect()); + + SkColor color = 0xFF000000; + int a0, a1; + + const SkPaint* saveLayerPaint0 = saveLayer0->paint(); + if (NULL != saveLayerPaint0) { + color = saveLayerPaint0->getColor(); + a0 = SkColorGetA(color); + } else { + a0 = 0xFF; + } + + const SkPaint* saveLayerPaint1 = saveLayer1->paint(); + if (NULL != saveLayerPaint1) { + color = saveLayerPaint1->getColor(); + a1 = SkColorGetA(color); + } else { + a1 = 0xFF; + } + + int newA = SkMulDiv255Round(a0, a1); + SkASSERT(newA <= 0xFF); + + SkPaint* dbmrPaint = dbmr->paint(); + + if (NULL != dbmrPaint) { + SkColor newColor = SkColorSetA(dbmrPaint->getColor(), newA); + dbmrPaint->setColor(newColor); + } else { + SkColor newColor = SkColorSetA(color, newA); + + SkPaint newPaint; + newPaint.setColor(newColor); + dbmr->setPaint(newPaint); + } + + // remove everything except the drawbitmaprect + canvas->deleteDrawCommandAt(curCommand+13); // restore + canvas->deleteDrawCommandAt(curCommand+12); // restore + canvas->deleteDrawCommandAt(curCommand+11); // restore + canvas->deleteDrawCommandAt(curCommand+10); // restore + canvas->deleteDrawCommandAt(curCommand+9); // restore + canvas->deleteDrawCommandAt(curCommand+7); // clipRect + canvas->deleteDrawCommandAt(curCommand+6); // save + canvas->deleteDrawCommandAt(curCommand+5); // saveLayer + canvas->deleteDrawCommandAt(curCommand+4); // clipRect + canvas->deleteDrawCommandAt(curCommand+3); // save + canvas->deleteDrawCommandAt(curCommand+2); // saveLayer + canvas->deleteDrawCommandAt(curCommand+1); // clipRect + canvas->deleteDrawCommandAt(curCommand); // save +} + +// Check for: +// SAVE +// CLIP_RECT +// DRAWBITMAPRECTTORECT +// RESTORE +// where: +// the drawBitmapRectToRect is a 1-1 copy from src to dest +// the clip rect is BW and a subset of the drawBitmapRectToRect's dest rect +static bool check_8(SkDebugCanvas* canvas, int curCommand) { + if (SAVE != canvas->getDrawCommandAt(curCommand)->getType() || + canvas->getSize() <= curCommand+4 || + CLIP_RECT != canvas->getDrawCommandAt(curCommand+1)->getType() || + DRAW_BITMAP_RECT_TO_RECT != canvas->getDrawCommandAt(curCommand+2)->getType() || + RESTORE != canvas->getDrawCommandAt(curCommand+3)->getType()) { + return false; + } + + SkClipRectCommand* clip = + (SkClipRectCommand*) canvas->getDrawCommandAt(curCommand+1); + SkDrawBitmapRectCommand* dbmr = + (SkDrawBitmapRectCommand*) canvas->getDrawCommandAt(curCommand+2); + + if (clip->doAA() || SkRegion::kIntersect_Op != clip->op()) { + return false; + } + + // The src->dest mapping needs to be 1-to-1 + if (NULL == dbmr->srcRect()) { + if (dbmr->bitmap().width() != dbmr->dstRect().width() || + dbmr->bitmap().height() != dbmr->dstRect().height()) { + return false; + } + } else { + if (dbmr->srcRect()->width() != dbmr->dstRect().width() || + dbmr->srcRect()->height() != dbmr->dstRect().height()) { + return false; + } + } + + if (!dbmr->dstRect().contains(clip->rect())) { + return false; + } + + return true; +} + +// Fold the clipRect into the drawBitmapRectToRect's src and dest rects +static void apply_8(SkDebugCanvas* canvas, int curCommand) { + SkClipRectCommand* clip = + (SkClipRectCommand*) canvas->getDrawCommandAt(curCommand+1); + SkDrawBitmapRectCommand* dbmr = + (SkDrawBitmapRectCommand*) canvas->getDrawCommandAt(curCommand+2); + + SkScalar newSrcLeft, newSrcTop; + + if (NULL != dbmr->srcRect()) { + newSrcLeft = dbmr->srcRect()->fLeft + clip->rect().fLeft - dbmr->dstRect().fLeft; + newSrcTop = dbmr->srcRect()->fTop + clip->rect().fTop - dbmr->dstRect().fTop; + } else { + newSrcLeft = clip->rect().fLeft - dbmr->dstRect().fLeft; + newSrcTop = clip->rect().fTop - dbmr->dstRect().fTop; + } + + SkRect newSrc = SkRect::MakeXYWH(newSrcLeft, newSrcTop, + clip->rect().width(), clip->rect().height()); + + dbmr->setSrcRect(newSrc); + dbmr->setDstRect(clip->rect()); + + // remove everything except the drawbitmaprect + canvas->deleteDrawCommandAt(curCommand+3); + canvas->deleteDrawCommandAt(curCommand+1); + canvas->deleteDrawCommandAt(curCommand); +} + +// Check for: +// SAVE +// CLIP_RECT +// DRAWBITMAPRECTTORECT +// RESTORE +// where: +// clipRect is BW and encloses the DBMR2R's dest rect +static bool check_9(SkDebugCanvas* canvas, int curCommand) { + if (SAVE != canvas->getDrawCommandAt(curCommand)->getType() || + canvas->getSize() <= curCommand+4 || + CLIP_RECT != canvas->getDrawCommandAt(curCommand+1)->getType() || + DRAW_BITMAP_RECT_TO_RECT != canvas->getDrawCommandAt(curCommand+2)->getType() || + RESTORE != canvas->getDrawCommandAt(curCommand+3)->getType()) { + return false; + } + + SkClipRectCommand* clip = + (SkClipRectCommand*) canvas->getDrawCommandAt(curCommand+1); + SkDrawBitmapRectCommand* dbmr = + (SkDrawBitmapRectCommand*) canvas->getDrawCommandAt(curCommand+2); + + if (clip->doAA() || SkRegion::kIntersect_Op != clip->op()) { + return false; + } + + if (!clip->rect().contains(dbmr->dstRect())) { + return false; + } + + return true; +} + +// remove everything except the drawbitmaprect +static void apply_9(SkDebugCanvas* canvas, int curCommand) { + canvas->deleteDrawCommandAt(curCommand+3); // restore + // drawBitmapRectToRect + canvas->deleteDrawCommandAt(curCommand+1); // clipRect + canvas->deleteDrawCommandAt(curCommand); // save +} + +typedef bool (*PFCheck)(SkDebugCanvas* canvas, int curCommand); +typedef void (*PFApply)(SkDebugCanvas* canvas, int curCommand); + +struct OptTableEntry { + PFCheck fCheck; + PFApply fApply; + int fNumTimesApplied; +} gOptTable[] = { + { check_0, apply_0, 0 }, + { check_1, apply_1, 0 }, + { check_2, apply_2, 0 }, + { check_3, apply_3, 0 }, + { check_4, apply_4, 0 }, + { check_5, apply_5, 0 }, + { check_6, apply_6, 0 }, + { check_7, apply_7, 0 }, + { check_8, apply_8, 0 }, + { check_9, apply_9, 0 }, +}; + + +static int filter_picture(const SkString& inFile, const SkString& outFile) { + SkAutoTDelete<SkPicture> inPicture; + + SkFILEStream inStream(inFile.c_str()); + if (inStream.isValid()) { + inPicture.reset(SkPicture::CreateFromStream(&inStream)); + } + + if (NULL == inPicture.get()) { + SkDebugf("Could not read file %s\n", inFile.c_str()); + return -1; + } + + int localCount[SK_ARRAY_COUNT(gOptTable)]; + + memset(localCount, 0, sizeof(localCount)); + + SkDebugCanvas debugCanvas(inPicture->width(), inPicture->height()); + debugCanvas.setBounds(inPicture->width(), inPicture->height()); + inPicture->draw(&debugCanvas); + + // delete the initial save and restore since replaying the commands will + // re-add them + if (debugCanvas.getSize() > 1) { + debugCanvas.deleteDrawCommandAt(0); + debugCanvas.deleteDrawCommandAt(debugCanvas.getSize()-1); + } + + bool changed = true; + int numBefore = debugCanvas.getSize(); + + while (changed) { + changed = false; + for (int i = 0; i < debugCanvas.getSize(); ++i) { + for (size_t opt = 0; opt < SK_ARRAY_COUNT(gOptTable); ++opt) { + if ((*gOptTable[opt].fCheck)(&debugCanvas, i)) { + (*gOptTable[opt].fApply)(&debugCanvas, i); + + ++gOptTable[opt].fNumTimesApplied; + ++localCount[opt]; + + if (debugCanvas.getSize() == i) { + // the optimization removed all the remaining operations + break; + } + + opt = 0; // try all the opts all over again + changed = true; + } + } + } + } + + int numAfter = debugCanvas.getSize(); + + if (!outFile.isEmpty()) { + SkPictureRecorder recorder; + SkCanvas* canvas = recorder.beginRecording(inPicture->width(), inPicture->height(), NULL, 0); + debugCanvas.draw(canvas); + SkAutoTUnref<SkPicture> outPicture(recorder.endRecording()); + + SkFILEWStream outStream(outFile.c_str()); + + outPicture->serialize(&outStream); + } + + bool someOptFired = false; + for (size_t opt = 0; opt < SK_ARRAY_COUNT(gOptTable); ++opt) { + if (0 != localCount[opt]) { + SkDebugf("%d: %d ", opt, localCount[opt]); + someOptFired = true; + } + } + + if (!someOptFired) { + SkDebugf("No opts fired\n"); + } else { + SkDebugf("\t before: %d after: %d delta: %d\n", + numBefore, numAfter, numBefore-numAfter); + } + + return 0; +} + +// This function is not marked as 'static' so it can be referenced externally +// in the iOS build. +int tool_main(int argc, char** argv); // suppress a warning on mac + +int tool_main(int argc, char** argv) { +#if SK_ENABLE_INST_COUNT + gPrintInstCount = true; +#endif + + SkGraphics::Init(); + + if (argc < 3) { + usage(); + return -1; + } + + SkString inFile, outFile, inDir, outDir; + + char* const* stop = argv + argc; + for (++argv; argv < stop; ++argv) { + if (strcmp(*argv, "-i") == 0) { + argv++; + if (argv < stop && **argv) { + inFile.set(*argv); + } else { + SkDebugf("missing arg for -i\n"); + usage(); + return -1; + } + } else if (strcmp(*argv, "--input-dir") == 0) { + argv++; + if (argv < stop && **argv) { + inDir.set(*argv); + } else { + SkDebugf("missing arg for --input-dir\n"); + usage(); + return -1; + } + } else if (strcmp(*argv, "--output-dir") == 0) { + argv++; + if (argv < stop && **argv) { + outDir.set(*argv); + } else { + SkDebugf("missing arg for --output-dir\n"); + usage(); + return -1; + } + } else if (strcmp(*argv, "-o") == 0) { + argv++; + if (argv < stop && **argv) { + outFile.set(*argv); + } else { + SkDebugf("missing arg for -o\n"); + usage(); + return -1; + } + } else if (strcmp(*argv, "--help") == 0 || strcmp(*argv, "-h") == 0) { + usage(); + return 0; + } else { + SkDebugf("unknown arg %s\n", *argv); + usage(); + return -1; + } + } + + SkOSFile::Iter iter(inDir.c_str(), "skp"); + + SkString inputFilename, outputFilename; + if (iter.next(&inputFilename)) { + + do { + inFile = SkOSPath::SkPathJoin(inDir.c_str(), inputFilename.c_str()); + if (!outDir.isEmpty()) { + outFile = SkOSPath::SkPathJoin(outDir.c_str(), inputFilename.c_str()); + } + SkDebugf("Executing %s\n", inputFilename.c_str()); + filter_picture(inFile, outFile); + } while(iter.next(&inputFilename)); + + } else if (!inFile.isEmpty()) { + filter_picture(inFile, outFile); + } else { + usage(); + return -1; + } + + for (size_t opt = 0; opt < SK_ARRAY_COUNT(gOptTable); ++opt) { + SkDebugf("opt %d: %d\n", opt, gOptTable[opt].fNumTimesApplied); + } + + SkGraphics::Term(); + return 0; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/find_bad_images_in_skps.py b/chromium/third_party/skia/tools/find_bad_images_in_skps.py new file mode 100755 index 00000000000..5dcf6a477e3 --- /dev/null +++ b/chromium/third_party/skia/tools/find_bad_images_in_skps.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python + +# Copyright 2013 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +This script will take as an argument either a list of skp files or a +set of directories that contains skp files. It will then test each +skp file with the `render_pictures` program. If that program either +spits out any unexpected output or doesn't return 0, I will flag that +skp file as problematic. We then extract all of the embedded images +inside the skp and test each one of them against the +SkImageDecoder::DecodeFile function. Again, we consider any +extraneous output or a bad return value an error. In the event of an +error, we retain the image and print out information about the error. +The output (on stdout) is formatted as a csv document. + +A copy of each bad image is left in a directory created by +tempfile.mkdtemp(). +""" + +import glob +import os +import re +import shutil +import subprocess +import sys +import tempfile +import threading + +import test_rendering # skia/trunk/tools. reuse FindPathToProgram() + +USAGE = """ +Usage: + {command} SKP_FILE [SKP_FILES] + {command} SKP_DIR [SKP_DIRS]\n +Environment variables: + To run multiple worker threads, set NUM_THREADS. + To use a different temporary storage location, set TMPDIR. + +""" + +def execute_program(args, ignores=None): + """ + Execute a process and waits for it to complete. Returns all + output (stderr and stdout) after (optional) filtering. + + @param args is passed into subprocess.Popen(). + + @param ignores (optional) is a list of regular expression strings + that will be ignored in the output. + + @returns a tuple (returncode, output) + """ + if ignores is None: + ignores = [] + else: + ignores = [re.compile(ignore) for ignore in ignores] + proc = subprocess.Popen( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + output = ''.join( + line for line in proc.stdout + if not any(bool(ignore.match(line)) for ignore in ignores)) + returncode = proc.wait() + return (returncode, output) + + +def list_files(paths): + """ + Accepts a list of directories or filenames on the command line. + We do not choose to recurse into directories beyond one level. + """ + class NotAFileException(Exception): + pass + for path in paths: + for globbedpath in glob.iglob(path): # useful on win32 + if os.path.isdir(globbedpath): + for filename in os.listdir(globbedpath): + newpath = os.path.join(globbedpath, filename) + if os.path.isfile(newpath): + yield newpath + elif os.path.isfile(globbedpath): + yield globbedpath + else: + raise NotAFileException('{} is not a file'.format(globbedpath)) + + +class BadImageFinder(object): + + def __init__(self, directory=None): + self.render_pictures = test_rendering.FindPathToProgram( + 'render_pictures') + self.test_image_decoder = test_rendering.FindPathToProgram( + 'test_image_decoder') + assert os.path.isfile(self.render_pictures) + assert os.path.isfile(self.test_image_decoder) + if directory is None: + self.saved_image_dir = tempfile.mkdtemp(prefix='skia_skp_test_') + else: + assert os.path.isdir(directory) + self.saved_image_dir = directory + self.bad_image_count = 0 + + def process_files(self, skp_files): + for path in skp_files: + self.process_file(path) + + def process_file(self, skp_file): + assert self.saved_image_dir is not None + assert os.path.isfile(skp_file) + args = [self.render_pictures, '--readPath', skp_file] + ignores = ['^process_in', '^deserializ', '^drawing...', '^Non-defaul'] + returncode, output = execute_program(args, ignores) + if (returncode == 0) and not output: + return + temp_image_dir = tempfile.mkdtemp(prefix='skia_skp_test___') + args = [ self.render_pictures, '--readPath', skp_file, + '--writePath', temp_image_dir, '--writeEncodedImages'] + subprocess.call(args, stderr=open(os.devnull,'w'), + stdout=open(os.devnull,'w')) + for image_name in os.listdir(temp_image_dir): + image_path = os.path.join(temp_image_dir, image_name) + assert(os.path.isfile(image_path)) + args = [self.test_image_decoder, image_path] + returncode, output = execute_program(args, []) + if (returncode == 0) and not output: + os.remove(image_path) + continue + try: + shutil.move(image_path, self.saved_image_dir) + except (shutil.Error,): + # If this happens, don't stop the entire process, + # just warn the user. + os.remove(image_path) + sys.stderr.write('{0} is a repeat.\n'.format(image_name)) + self.bad_image_count += 1 + if returncode == 2: + returncode = 'SkImageDecoder::DecodeFile returns false' + elif returncode == 0: + returncode = 'extra verbosity' + assert output + elif returncode == -11: + returncode = 'segmentation violation' + else: + returncode = 'returncode: {}'.format(returncode) + output = output.strip().replace('\n',' ').replace('"','\'') + suffix = image_name[-3:] + output_line = '"{0}","{1}","{2}","{3}","{4}"\n'.format( + returncode, suffix, skp_file, image_name, output) + sys.stdout.write(output_line) + sys.stdout.flush() + os.rmdir(temp_image_dir) + return + +def main(main_argv): + if not main_argv or main_argv[0] in ['-h', '-?', '-help', '--help']: + sys.stderr.write(USAGE.format(command=__file__)) + return 1 + if 'NUM_THREADS' in os.environ: + number_of_threads = int(os.environ['NUM_THREADS']) + if number_of_threads < 1: + number_of_threads = 1 + else: + number_of_threads = 1 + os.environ['skia_images_png_suppressDecoderWarnings'] = 'true' + os.environ['skia_images_jpeg_suppressDecoderWarnings'] = 'true' + + temp_dir = tempfile.mkdtemp(prefix='skia_skp_test_') + sys.stderr.write('Directory for bad images: {}\n'.format(temp_dir)) + sys.stdout.write('"Error","Filetype","SKP File","Image File","Output"\n') + sys.stdout.flush() + + finders = [ + BadImageFinder(temp_dir) for index in xrange(number_of_threads)] + arguments = [[] for index in xrange(number_of_threads)] + for index, item in enumerate(list_files(main_argv)): + ## split up the given targets among the worker threads + arguments[index % number_of_threads].append(item) + threads = [ + threading.Thread( + target=BadImageFinder.process_files, args=(finder,argument)) + for finder, argument in zip(finders, arguments)] + for thread in threads: + thread.start() + for thread in threads: + thread.join() + number = sum(finder.bad_image_count for finder in finders) + sys.stderr.write('Number of bad images found: {}\n'.format(number)) + return 0 + +if __name__ == '__main__': + exit(main(sys.argv[1:])) + +# LocalWords: skp stdout csv diff --git a/chromium/third_party/skia/tools/find_run_binary.py b/chromium/third_party/skia/tools/find_run_binary.py new file mode 100644 index 00000000000..f71a808666b --- /dev/null +++ b/chromium/third_party/skia/tools/find_run_binary.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +# Copyright (c) 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Module that finds and runs a binary by looking in the likely locations.""" + + +import os +import subprocess +import sys + + +def run_command(args): + """Runs a program from the command line and returns stdout. + + Args: + args: Command line to run, as a list of string parameters. args[0] is the + binary to run. + + Returns: + stdout from the program, as a single string. + + Raises: + Exception: the program exited with a nonzero return code. + """ + proc = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stdout, stderr) = proc.communicate() + if proc.returncode is not 0: + raise Exception('command "%s" failed: %s' % (args, stderr)) + return stdout + + +def find_path_to_program(program): + """Returns path to an existing program binary. + + Args: + program: Basename of the program to find (e.g., 'render_pictures'). + + Returns: + Absolute path to the program binary, as a string. + + Raises: + Exception: unable to find the program binary. + """ + trunk_path = os.path.abspath(os.path.join(os.path.dirname(__file__), + os.pardir)) + possible_paths = [os.path.join(trunk_path, 'out', 'Release', program), + os.path.join(trunk_path, 'out', 'Debug', program), + os.path.join(trunk_path, 'out', 'Release', + program + '.exe'), + os.path.join(trunk_path, 'out', 'Debug', + program + '.exe')] + for try_path in possible_paths: + if os.path.isfile(try_path): + return try_path + raise Exception('cannot find %s in paths %s; maybe you need to ' + 'build %s?' % (program, possible_paths, program)) + diff --git a/chromium/third_party/skia/tools/flags/SkCommandLineFlags.cpp b/chromium/third_party/skia/tools/flags/SkCommandLineFlags.cpp new file mode 100644 index 00000000000..9b31b657e56 --- /dev/null +++ b/chromium/third_party/skia/tools/flags/SkCommandLineFlags.cpp @@ -0,0 +1,356 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkCommandLineFlags.h" +#include "SkTDArray.h" + +DEFINE_string(undefok, "", "Silently ignore unknown flags listed here instead of crashing."); + +bool SkFlagInfo::CreateStringFlag(const char* name, const char* shortName, + SkCommandLineFlags::StringArray* pStrings, + const char* defaultValue, const char* helpString) { + SkFlagInfo* info = SkNEW_ARGS(SkFlagInfo, (name, shortName, kString_FlagType, helpString)); + info->fDefaultString.set(defaultValue); + + info->fStrings = pStrings; + SetDefaultStrings(pStrings, defaultValue); + return true; +} + +void SkFlagInfo::SetDefaultStrings(SkCommandLineFlags::StringArray* pStrings, + const char* defaultValue) { + pStrings->reset(); + if (NULL == defaultValue) { + return; + } + // If default is "", leave the array empty. + size_t defaultLength = strlen(defaultValue); + if (defaultLength > 0) { + const char* const defaultEnd = defaultValue + defaultLength; + const char* begin = defaultValue; + while (true) { + while (begin < defaultEnd && ' ' == *begin) { + begin++; + } + if (begin < defaultEnd) { + const char* end = begin + 1; + while (end < defaultEnd && ' ' != *end) { + end++; + } + size_t length = end - begin; + pStrings->append(begin, length); + begin = end + 1; + } else { + break; + } + } + } +} + +static bool string_is_in(const char* target, const char* set[], size_t len) { + for (size_t i = 0; i < len; i++) { + if (0 == strcmp(target, set[i])) { + return true; + } + } + return false; +} + +/** + * Check to see whether string represents a boolean value. + * @param string C style string to parse. + * @param result Pointer to a boolean which will be set to the value in the string, if the + * string represents a boolean. + * @param boolean True if the string represents a boolean, false otherwise. + */ +static bool parse_bool_arg(const char* string, bool* result) { + static const char* trueValues[] = { "1", "TRUE", "true" }; + if (string_is_in(string, trueValues, SK_ARRAY_COUNT(trueValues))) { + *result = true; + return true; + } + static const char* falseValues[] = { "0", "FALSE", "false" }; + if (string_is_in(string, falseValues, SK_ARRAY_COUNT(falseValues))) { + *result = false; + return true; + } + SkDebugf("Parameter \"%s\" not supported.\n", string); + return false; +} + +bool SkFlagInfo::match(const char* string) { + if (SkStrStartsWith(string, '-') && strlen(string) > 1) { + string++; + const SkString* compareName; + if (SkStrStartsWith(string, '-') && strlen(string) > 1) { + string++; + // There were two dashes. Compare against full name. + compareName = &fName; + } else { + // One dash. Compare against the short name. + compareName = &fShortName; + } + if (kBool_FlagType == fFlagType) { + // In this case, go ahead and set the value. + if (compareName->equals(string)) { + *fBoolValue = true; + return true; + } + if (SkStrStartsWith(string, "no") && strlen(string) > 2) { + string += 2; + // Only allow "no" to be prepended to the full name. + if (fName.equals(string)) { + *fBoolValue = false; + return true; + } + return false; + } + int equalIndex = SkStrFind(string, "="); + if (equalIndex > 0) { + // The string has an equal sign. Check to see if the string matches. + SkString flag(string, equalIndex); + if (flag.equals(*compareName)) { + // Check to see if the remainder beyond the equal sign is true or false: + string += equalIndex + 1; + parse_bool_arg(string, fBoolValue); + return true; + } else { + return false; + } + } + } + return compareName->equals(string); + } else { + // Has no dash + return false; + } + return false; +} + +SkFlagInfo* SkCommandLineFlags::gHead; +SkString SkCommandLineFlags::gUsage; + +void SkCommandLineFlags::SetUsage(const char* usage) { + gUsage.set(usage); +} + +// Maximum line length for the help message. +#define LINE_LENGTH 80 + +static void print_help_for_flag(const SkFlagInfo* flag) { + SkDebugf("\t--%s", flag->name().c_str()); + const SkString& shortName = flag->shortName(); + if (shortName.size() > 0) { + SkDebugf(" or -%s", shortName.c_str()); + } + SkDebugf(":\ttype: %s", flag->typeAsString().c_str()); + if (flag->defaultValue().size() > 0) { + SkDebugf("\tdefault: %s", flag->defaultValue().c_str()); + } + SkDebugf("\n"); + const SkString& help = flag->help(); + size_t length = help.size(); + const char* currLine = help.c_str(); + const char* stop = currLine + length; + while (currLine < stop) { + if (strlen(currLine) < LINE_LENGTH) { + // Only one line length's worth of text left. + SkDebugf("\t\t%s\n", currLine); + break; + } + int lineBreak = SkStrFind(currLine, "\n"); + if (lineBreak < 0 || lineBreak > LINE_LENGTH) { + // No line break within line length. Will need to insert one. + // Find a space before the line break. + int spaceIndex = LINE_LENGTH - 1; + while (spaceIndex > 0 && currLine[spaceIndex] != ' ') { + spaceIndex--; + } + int gap; + if (0 == spaceIndex) { + // No spaces on the entire line. Go ahead and break mid word. + spaceIndex = LINE_LENGTH; + gap = 0; + } else { + // Skip the space on the next line + gap = 1; + } + SkDebugf("\t\t%.*s\n", spaceIndex, currLine); + currLine += spaceIndex + gap; + } else { + // the line break is within the limit. Break there. + lineBreak++; + SkDebugf("\t\t%.*s", lineBreak, currLine); + currLine += lineBreak; + } + } + SkDebugf("\n"); +} + +void SkCommandLineFlags::Parse(int argc, char** argv) { + // Only allow calling this function once. + static bool gOnce; + if (gOnce) { + SkDebugf("Parse should only be called once at the beginning of main!\n"); + SkASSERT(false); + return; + } + gOnce = true; + + bool helpPrinted = false; + // Loop over argv, starting with 1, since the first is just the name of the program. + for (int i = 1; i < argc; i++) { + if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) { + // Print help message. + SkTDArray<const char*> helpFlags; + for (int j = i + 1; j < argc; j++) { + if (SkStrStartsWith(argv[j], '-')) { + break; + } + helpFlags.append(1, &argv[j]); + } + if (0 == helpFlags.count()) { + // Only print general help message if help for specific flags is not requested. + SkDebugf("%s\n%s\n", argv[0], gUsage.c_str()); + } + SkDebugf("Flags:\n"); + SkFlagInfo* flag = SkCommandLineFlags::gHead; + while (flag != NULL) { + // If no flags followed --help, print them all + bool printFlag = 0 == helpFlags.count(); + if (!printFlag) { + for (int k = 0; k < helpFlags.count(); k++) { + if (flag->name().equals(helpFlags[k]) || + flag->shortName().equals(helpFlags[k])) { + printFlag = true; + helpFlags.remove(k); + break; + } + } + } + if (printFlag) { + print_help_for_flag(flag); + } + flag = flag->next(); + } + if (helpFlags.count() > 0) { + SkDebugf("Requested help for unrecognized flags:\n"); + for (int k = 0; k < helpFlags.count(); k++) { + SkDebugf("\t--%s\n", helpFlags[k]); + } + } + helpPrinted = true; + } + if (!helpPrinted) { + bool flagMatched = false; + SkFlagInfo* flag = gHead; + while (flag != NULL) { + if (flag->match(argv[i])) { + flagMatched = true; + switch (flag->getFlagType()) { + case SkFlagInfo::kBool_FlagType: + // Can be handled by match, above, but can also be set by the next + // string. + if (i+1 < argc && !SkStrStartsWith(argv[i+1], '-')) { + i++; + bool value; + if (parse_bool_arg(argv[i], &value)) { + flag->setBool(value); + } + } + break; + case SkFlagInfo::kString_FlagType: + flag->resetStrings(); + // Add all arguments until another flag is reached. + while (i+1 < argc && !SkStrStartsWith(argv[i+1], '-')) { + i++; + flag->append(argv[i]); + } + break; + case SkFlagInfo::kInt_FlagType: + i++; + flag->setInt(atoi(argv[i])); + break; + case SkFlagInfo::kDouble_FlagType: + i++; + flag->setDouble(atof(argv[i])); + break; + default: + SkDEBUGFAIL("Invalid flag type"); + } + break; + } + flag = flag->next(); + } + if (!flagMatched) { + SkString stripped(argv[i]); + while (stripped.startsWith('-')) { + stripped.remove(0, 1); + } + if (!FLAGS_undefok.contains(stripped.c_str())) { + SkDebugf("Got unknown flag \"%s\". Exiting.\n", argv[i]); + exit(-1); + } + } + } + } + // Since all of the flags have been set, release the memory used by each + // flag. FLAGS_x can still be used after this. + SkFlagInfo* flag = gHead; + gHead = NULL; + while (flag != NULL) { + SkFlagInfo* next = flag->next(); + SkDELETE(flag); + flag = next; + } + if (helpPrinted) { + exit(0); + } +} + +namespace { + +template <typename Strings> +bool ShouldSkipImpl(const Strings& strings, const char* name) { + int count = strings.count(); + size_t testLen = strlen(name); + bool anyExclude = count == 0; + for (int i = 0; i < strings.count(); ++i) { + const char* matchName = strings[i]; + size_t matchLen = strlen(matchName); + bool matchExclude, matchStart, matchEnd; + if ((matchExclude = matchName[0] == '~')) { + anyExclude = true; + matchName++; + matchLen--; + } + if ((matchStart = matchName[0] == '^')) { + matchName++; + matchLen--; + } + if ((matchEnd = matchName[matchLen - 1] == '$')) { + matchLen--; + } + if (matchStart ? (!matchEnd || matchLen == testLen) + && strncmp(name, matchName, matchLen) == 0 + : matchEnd ? matchLen <= testLen + && strncmp(name + testLen - matchLen, matchName, matchLen) == 0 + : strstr(name, matchName) != 0) { + return matchExclude; + } + } + return !anyExclude; +} + +} // namespace + +bool SkCommandLineFlags::ShouldSkip(const SkTDArray<const char*>& strings, const char* name) { + return ShouldSkipImpl(strings, name); +} +bool SkCommandLineFlags::ShouldSkip(const StringArray& strings, const char* name) { + return ShouldSkipImpl(strings, name); +} diff --git a/chromium/third_party/skia/tools/flags/SkCommandLineFlags.h b/chromium/third_party/skia/tools/flags/SkCommandLineFlags.h new file mode 100644 index 00000000000..370198b5a20 --- /dev/null +++ b/chromium/third_party/skia/tools/flags/SkCommandLineFlags.h @@ -0,0 +1,446 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SK_COMMAND_LINE_FLAGS_H +#define SK_COMMAND_LINE_FLAGS_H + +#include "SkString.h" +#include "SkTArray.h" +#include "SkTDArray.h" + +/** + * Including this file (and compiling SkCommandLineFlags.cpp) provides command line + * parsing. In order to use it, use the following macros in global + * namespace: + * + * DEFINE_bool(name, defaultValue, helpString); + * DEFINE_string(name, defaultValue, helpString); + * DEFINE_int32(name, defaultValue, helpString); + * DEFINE_double(name, defaultValue, helpString); + * + * Then, in main, call SkCommandLineFlags::SetUsage() to describe usage and call + * SkCommandLineFlags::Parse() to parse the flags. Henceforth, each flag can + * be referenced using + * + * FLAGS_name + * + * For example, the line + * + * DEFINE_bool(boolean, false, "The variable boolean does such and such"); + * + * will create the following variable: + * + * bool FLAGS_boolean; + * + * which will initially be set to false, and can be set to true by using the + * flag "--boolean" on the commandline. "--noboolean" will set FLAGS_boolean + * to false. FLAGS_boolean can also be set using "--boolean=true" or + * "--boolean true" (where "true" can be replaced by "false", "TRUE", "FALSE", + * "1" or "0"). + * + * The helpString will be printed if the help flag (-h or -help) is used. + * + * Similarly, the line + * + * DEFINE_int32(integer, .., ..); + * + * will create + * + * int32_t FLAGS_integer; + * + * and + * + * DEFINE_double(real, .., ..); + * + * will create + * + * double FLAGS_real; + * + * These flags can be set by specifying, for example, "--integer 7" and + * "--real 3.14" on the command line. + * + * Unlike the others, the line + * + * DEFINE_string(args, .., ..); + * + * creates an array: + * + * SkCommandLineFlags::StringArray FLAGS_args; + * + * If the default value is the empty string, FLAGS_args will default to a size + * of zero. Otherwise it will default to a size of 1 with the default string + * as its value. All strings that follow the flag on the command line (until + * a string that begins with '-') will be entries in the array. + * + * Any flag can be referenced from another file after using the following: + * + * DECLARE_x(name); + * + * (where 'x' is the type specified in the DEFINE). + * + * Inspired by gflags (https://code.google.com/p/gflags/). Is not quite as + * robust as gflags, but suits our purposes. For example, allows creating + * a flag -h or -help which will never be used, since SkCommandLineFlags handles it. + * SkCommandLineFlags will also allow creating --flag and --noflag. Uses the same input + * format as gflags and creates similarly named variables (i.e. FLAGS_name). + * Strings are handled differently (resulting variable will be an array of + * strings) so that a flag can be followed by multiple parameters. + */ + +class SkFlagInfo; + +class SkCommandLineFlags { + +public: + /** + * Call to set the help message to be displayed. Should be called before + * Parse. + */ + static void SetUsage(const char* usage); + + /** + * Call at the beginning of main to parse flags created by DEFINE_x, above. + * Must only be called once. + */ + static void Parse(int argc, char** argv); + + /** + * Custom class for holding the arguments for a string flag. + * Publicly only has accessors so the strings cannot be modified. + */ + class StringArray { + public: + const char* operator[](int i) const { + SkASSERT(i >= 0 && i < fStrings.count()); + return fStrings[i].c_str(); + } + + int count() const { + return fStrings.count(); + } + + bool isEmpty() const { return this->count() == 0; } + + /** + * Returns true iff string is equal to one of the strings in this array. + */ + bool contains(const char* string) const { + for (int i = 0; i < fStrings.count(); i++) { + if (fStrings[i].equals(string)) { + return true; + } + } + return false; + } + + private: + void reset() { fStrings.reset(); } + + void append(const char* string) { + fStrings.push_back().set(string); + } + + void append(const char* string, size_t length) { + fStrings.push_back().set(string, length); + } + + SkTArray<SkString> fStrings; + + friend class SkFlagInfo; + }; + + /* Takes a list of the form [~][^]match[$] + ~ causes a matching test to always be skipped + ^ requires the start of the test to match + $ requires the end of the test to match + ^ and $ requires an exact match + If a test does not match any list entry, it is skipped unless some list entry starts with ~ + */ + static bool ShouldSkip(const SkTDArray<const char*>& strings, const char* name); + static bool ShouldSkip(const StringArray& strings, const char* name); + +private: + static SkFlagInfo* gHead; + static SkString gUsage; + + // For access to gHead. + friend class SkFlagInfo; +}; + +#define TO_STRING2(s) #s +#define TO_STRING(s) TO_STRING2(s) + +#define DEFINE_bool(name, defaultValue, helpString) \ +bool FLAGS_##name; \ +static bool unused_##name = SkFlagInfo::CreateBoolFlag(TO_STRING(name), \ + NULL, \ + &FLAGS_##name, \ + defaultValue, \ + helpString) + +// bool 2 allows specifying a short name. No check is done to ensure that shortName +// is actually shorter than name. +#define DEFINE_bool2(name, shortName, defaultValue, helpString) \ +bool FLAGS_##name; \ +static bool unused_##name = SkFlagInfo::CreateBoolFlag(TO_STRING(name), \ + TO_STRING(shortName),\ + &FLAGS_##name, \ + defaultValue, \ + helpString) + +#define DECLARE_bool(name) extern bool FLAGS_##name; + +#define DEFINE_string(name, defaultValue, helpString) \ +SkCommandLineFlags::StringArray FLAGS_##name; \ +static bool unused_##name = SkFlagInfo::CreateStringFlag(TO_STRING(name), \ + NULL, \ + &FLAGS_##name, \ + defaultValue, \ + helpString) + +// string2 allows specifying a short name. There is an assert that shortName +// is only 1 character. +#define DEFINE_string2(name, shortName, defaultValue, helpString) \ +SkCommandLineFlags::StringArray FLAGS_##name; \ +static bool unused_##name = SkFlagInfo::CreateStringFlag(TO_STRING(name), \ + TO_STRING(shortName), \ + &FLAGS_##name, \ + defaultValue, \ + helpString) + +#define DECLARE_string(name) extern SkCommandLineFlags::StringArray FLAGS_##name; + +#define DEFINE_int32(name, defaultValue, helpString) \ +int32_t FLAGS_##name; \ +static bool unused_##name = SkFlagInfo::CreateIntFlag(TO_STRING(name), \ + &FLAGS_##name, \ + defaultValue, \ + helpString) + +#define DECLARE_int32(name) extern int32_t FLAGS_##name; + +#define DEFINE_double(name, defaultValue, helpString) \ +double FLAGS_##name; \ +static bool unused_##name = SkFlagInfo::CreateDoubleFlag(TO_STRING(name), \ + &FLAGS_##name, \ + defaultValue, \ + helpString) + +#define DECLARE_double(name) extern double FLAGS_##name; + +class SkFlagInfo { + +public: + enum FlagTypes { + kBool_FlagType, + kString_FlagType, + kInt_FlagType, + kDouble_FlagType, + }; + + /** + * Each Create<Type>Flag function creates an SkFlagInfo of the specified type. The SkFlagInfo + * object is appended to a list, which is deleted when SkCommandLineFlags::Parse is called. + * Therefore, each call should be made before the call to ::Parse. They are not intended + * to be called directly. Instead, use the macros described above. + * @param name Long version (at least 2 characters) of the name of the flag. This name can + * be referenced on the command line as "--name" to set the value of this flag. + * @param shortName Short version (one character) of the name of the flag. This name can + * be referenced on the command line as "-shortName" to set the value of this flag. + * @param p<Type> Pointer to a global variable which holds the value set by SkCommandLineFlags. + * @param defaultValue The default value of this flag. The variable pointed to by p<Type> will + * be set to this value initially. This is also displayed as part of the help output. + * @param helpString Explanation of what this flag changes in the program. + */ + static bool CreateBoolFlag(const char* name, const char* shortName, bool* pBool, + bool defaultValue, const char* helpString) { + SkFlagInfo* info = SkNEW_ARGS(SkFlagInfo, (name, shortName, kBool_FlagType, helpString)); + info->fBoolValue = pBool; + *info->fBoolValue = info->fDefaultBool = defaultValue; + return true; + } + + /** + * See comments for CreateBoolFlag. + * @param pStrings Unlike the others, this is a pointer to an array of values. + * @param defaultValue Thise default will be parsed so that strings separated by spaces + * will be added to pStrings. + */ + static bool CreateStringFlag(const char* name, const char* shortName, + SkCommandLineFlags::StringArray* pStrings, + const char* defaultValue, const char* helpString); + + /** + * See comments for CreateBoolFlag. + */ + static bool CreateIntFlag(const char* name, int32_t* pInt, + int32_t defaultValue, const char* helpString) { + SkFlagInfo* info = SkNEW_ARGS(SkFlagInfo, (name, NULL, kInt_FlagType, helpString)); + info->fIntValue = pInt; + *info->fIntValue = info->fDefaultInt = defaultValue; + return true; + } + + /** + * See comments for CreateBoolFlag. + */ + static bool CreateDoubleFlag(const char* name, double* pDouble, + double defaultValue, const char* helpString) { + SkFlagInfo* info = SkNEW_ARGS(SkFlagInfo, (name, NULL, kDouble_FlagType, helpString)); + info->fDoubleValue = pDouble; + *info->fDoubleValue = info->fDefaultDouble = defaultValue; + return true; + } + + /** + * Returns true if the string matches this flag. + * For a boolean flag, also sets the value, since a boolean flag can be set in a number of ways + * without looking at the following string: + * --name + * --noname + * --name=true + * --name=false + * --name=1 + * --name=0 + * --name=TRUE + * --name=FALSE + */ + bool match(const char* string); + + FlagTypes getFlagType() const { return fFlagType; } + + void resetStrings() { + if (kString_FlagType == fFlagType) { + fStrings->reset(); + } else { + SkDEBUGFAIL("Can only call resetStrings on kString_FlagType"); + } + } + + void append(const char* string) { + if (kString_FlagType == fFlagType) { + fStrings->append(string); + } else { + SkDEBUGFAIL("Can only append to kString_FlagType"); + } + } + + void setInt(int32_t value) { + if (kInt_FlagType == fFlagType) { + *fIntValue = value; + } else { + SkDEBUGFAIL("Can only call setInt on kInt_FlagType"); + } + } + + void setDouble(double value) { + if (kDouble_FlagType == fFlagType) { + *fDoubleValue = value; + } else { + SkDEBUGFAIL("Can only call setDouble on kDouble_FlagType"); + } + } + + void setBool(bool value) { + if (kBool_FlagType == fFlagType) { + *fBoolValue = value; + } else { + SkDEBUGFAIL("Can only call setBool on kBool_FlagType"); + } + } + + SkFlagInfo* next() { return fNext; } + + const SkString& name() const { return fName; } + + const SkString& shortName() const { return fShortName; } + + const SkString& help() const { return fHelpString; } + + SkString defaultValue() const { + SkString result; + switch (fFlagType) { + case SkFlagInfo::kBool_FlagType: + result.printf("%s", fDefaultBool ? "true" : "false"); + break; + case SkFlagInfo::kString_FlagType: + return fDefaultString; + case SkFlagInfo::kInt_FlagType: + result.printf("%i", fDefaultInt); + break; + case SkFlagInfo::kDouble_FlagType: + result.printf("%2.2f", fDefaultDouble); + break; + default: + SkDEBUGFAIL("Invalid flag type"); + } + return result; + } + + SkString typeAsString() const { + switch (fFlagType) { + case SkFlagInfo::kBool_FlagType: + return SkString("bool"); + case SkFlagInfo::kString_FlagType: + return SkString("string"); + case SkFlagInfo::kInt_FlagType: + return SkString("int"); + case SkFlagInfo::kDouble_FlagType: + return SkString("double"); + default: + SkDEBUGFAIL("Invalid flag type"); + return SkString(); + } + } + +private: + SkFlagInfo(const char* name, const char* shortName, FlagTypes type, const char* helpString) + : fName(name) + , fShortName(shortName) + , fFlagType(type) + , fHelpString(helpString) + , fBoolValue(NULL) + , fDefaultBool(false) + , fIntValue(NULL) + , fDefaultInt(0) + , fDoubleValue(NULL) + , fDefaultDouble(0) + , fStrings(NULL) { + fNext = SkCommandLineFlags::gHead; + SkCommandLineFlags::gHead = this; + SkASSERT(NULL != name && strlen(name) > 1); + SkASSERT(NULL == shortName || 1 == strlen(shortName)); + } + + /** + * Set a StringArray to hold the values stored in defaultStrings. + * @param array The StringArray to modify. + * @param defaultStrings Space separated list of strings that should be inserted into array + * individually. + */ + static void SetDefaultStrings(SkCommandLineFlags::StringArray* array, + const char* defaultStrings); + + // Name of the flag, without initial dashes + SkString fName; + SkString fShortName; + FlagTypes fFlagType; + SkString fHelpString; + bool* fBoolValue; + bool fDefaultBool; + int32_t* fIntValue; + int32_t fDefaultInt; + double* fDoubleValue; + double fDefaultDouble; + SkCommandLineFlags::StringArray* fStrings; + // Both for the help string and in case fStrings is empty. + SkString fDefaultString; + + // In order to keep a linked list. + SkFlagInfo* fNext; +}; +#endif // SK_COMMAND_LINE_FLAGS_H diff --git a/chromium/third_party/skia/tools/gcov_shim b/chromium/third_party/skia/tools/gcov_shim new file mode 100755 index 00000000000..1bac3b7f027 --- /dev/null +++ b/chromium/third_party/skia/tools/gcov_shim @@ -0,0 +1,15 @@ +#!/bin/bash + +# Running gcov with -a (--all-blocks) will hang on some files. lcov uses -a. +# This shim strips out that flag (a minor feature) so we can run gcov. + +CMD="gcov" + +while (( "$#" )); do + if [[ "$1" != "-a" && "$1" != "-all-blocks" && "$1" != "--all-blocks" ]]; then + CMD="$CMD $1" + fi + shift +done + +$CMD diff --git a/chromium/third_party/skia/tools/gen_bench_expectations_from_codereview.py b/chromium/third_party/skia/tools/gen_bench_expectations_from_codereview.py new file mode 100644 index 00000000000..3d40aa67eb0 --- /dev/null +++ b/chromium/third_party/skia/tools/gen_bench_expectations_from_codereview.py @@ -0,0 +1,183 @@ +#!/usr/bin/python + +# Copyright (c) 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +"""Generate new bench expectations from results of trybots on a code review.""" + + +import collections +import compare_codereview +import os +import re +import shutil +import subprocess +import sys + + +BENCH_DATA_URL = 'gs://chromium-skia-gm/perfdata/%s/%s/*' +CHECKOUT_PATH = os.path.realpath(os.path.join( + os.path.dirname(os.path.abspath(__file__)), os.pardir)) +TMP_BENCH_DATA_DIR = os.path.join(CHECKOUT_PATH, '.bench_data') + + +TryBuild = collections.namedtuple( + 'TryBuild', ['builder_name', 'build_number', 'is_finished']) + + +def find_all_builds(codereview_url): + """Finds and returns information about trybot runs for a code review. + + Args: + codereview_url: URL of the codereview in question. + + Returns: + List of NamedTuples: (builder_name, build_number, is_finished) + """ + results = compare_codereview.CodeReviewHTMLParser().parse(codereview_url) + try_builds = [] + for builder, data in results.iteritems(): + if builder.startswith('Perf'): + build_num = data.url.split('/')[-1] if data.url else None + is_finished = (data.status not in ('pending', 'try-pending') and + build_num is not None) + try_builds.append(TryBuild(builder_name=builder, + build_number=build_num, + is_finished=is_finished)) + return try_builds + + +def _all_trybots_finished(try_builds): + """Return True iff all of the given try jobs have finished. + + Args: + try_builds: list of TryBuild instances. + + Returns: + True if all of the given try jobs have finished, otherwise False. + """ + for try_build in try_builds: + if not try_build.is_finished: + return False + return True + + +def all_trybots_finished(codereview_url): + """Return True iff all of the try jobs on the given codereview have finished. + + Args: + codereview_url: string; URL of the codereview. + + Returns: + True if all of the try jobs have finished, otherwise False. + """ + return _all_trybots_finished(find_all_builds(codereview_url)) + + +def get_bench_data(builder, build_num, dest_dir): + """Download the bench data for the given builder at the given build_num. + + Args: + builder: string; name of the builder. + build_num: string; build number. + dest_dir: string; destination directory for the bench data. + """ + url = BENCH_DATA_URL % (builder, build_num) + subprocess.check_call(['gsutil', 'cp', '-R', url, dest_dir], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + +def find_revision_from_downloaded_data(dest_dir): + """Finds the revision at which the downloaded data was generated. + + Args: + dest_dir: string; directory holding the downloaded data. + + Returns: + The revision (git commit hash) at which the downloaded data was + generated, or None if no revision can be found. + """ + for data_file in os.listdir(dest_dir): + match = re.match('bench_(?P<revision>[0-9a-fA-F]{2,40})_data.*', data_file) + if match: + return match.group('revision') + return None + + +class TrybotNotFinishedError(Exception): + pass + + +def gen_bench_expectations_from_codereview(codereview_url, + error_on_unfinished=True): + """Generate bench expectations from a code review. + + Scans the given code review for Perf trybot runs. Downloads the results of + finished trybots and uses them to generate new expectations for their + waterfall counterparts. + + Args: + url: string; URL of the code review. + error_on_unfinished: bool; throw an error if any trybot has not finished. + """ + try_builds = find_all_builds(codereview_url) + + # Verify that all trybots have finished running. + if error_on_unfinished and not _all_trybots_finished(try_builds): + raise TrybotNotFinishedError('Not all trybots have finished.') + + failed_data_pull = [] + failed_gen_expectations = [] + + if os.path.isdir(TMP_BENCH_DATA_DIR): + shutil.rmtree(TMP_BENCH_DATA_DIR) + + for try_build in try_builds: + try_builder = try_build.builder_name + builder = try_builder.replace('-Trybot', '') + + # Download the data. + dest_dir = os.path.join(TMP_BENCH_DATA_DIR, builder) + os.makedirs(dest_dir) + try: + get_bench_data(try_builder, try_build.build_number, dest_dir) + except subprocess.CalledProcessError: + failed_data_pull.append(try_builder) + continue + + # Find the revision at which the data was generated. + revision = find_revision_from_downloaded_data(dest_dir) + if not revision: + # If we can't find a revision, then something is wrong with the data we + # downloaded. Skip this builder. + failed_data_pull.append(try_builder) + continue + + # Generate new expectations. + output_file = os.path.join(CHECKOUT_PATH, 'expectations', 'bench', + 'bench_expectations_%s.txt' % builder) + try: + subprocess.check_call(['python', + os.path.join(CHECKOUT_PATH, 'bench', + 'gen_bench_expectations.py'), + '-b', builder, '-o', output_file, + '-d', dest_dir, '-r', revision]) + except subprocess.CalledProcessError: + failed_gen_expectations.append(builder) + + failure = '' + if failed_data_pull: + failure += 'Failed to load data for: %s\n\n' % ','.join(failed_data_pull) + if failed_gen_expectations: + failure += 'Failed to generate expectations for: %s\n\n' % ','.join( + failed_gen_expectations) + if failure: + raise Exception(failure) + + +if __name__ == '__main__': + gen_bench_expectations_from_codereview(sys.argv[1]) + diff --git a/chromium/third_party/skia/tools/generate_fir_coeff.py b/chromium/third_party/skia/tools/generate_fir_coeff.py new file mode 100644 index 00000000000..70f521fdafc --- /dev/null +++ b/chromium/third_party/skia/tools/generate_fir_coeff.py @@ -0,0 +1,119 @@ +#!/usr/bin/python + +''' +Copyright 2013 Google Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +''' + +import math +import pprint + +def withinStdDev(n): + """Returns the percent of samples within n std deviations of the normal.""" + return math.erf(n / math.sqrt(2)) + +def withinStdDevRange(a, b): + """Returns the percent of samples within the std deviation range a, b""" + if b < a: + return 0; + + if a < 0: + if b < 0: + return (withinStdDev(-a) - withinStdDev(-b)) / 2; + else: + return (withinStdDev(-a) + withinStdDev(b)) / 2; + else: + return (withinStdDev(b) - withinStdDev(a)) / 2; + + +#We have a bunch of smudged samples which represent the average coverage of a range. +#We have a 'center' which may not line up with those samples. +#From the 'center' we want to make a normal approximation where '5' sample width out we're at '3' std deviations. +#The first and last samples may not be fully covered. + +#This is the sub-sample shift for each set of FIR coefficients (the centers of the lcds in the samples) +#Each subpxl takes up 1/3 of a pixel, so they are centered at x=(i/n+1/2n), or 1/6, 3/6, 5/6 of a pixel. +#Each sample takes up 1/4 of a pixel, so the results fall at (x*4)%1, or 2/3, 0, 1/3 of a sample. +samples_per_pixel = 4 +subpxls_per_pixel = 3 +#sample_offsets is (frac, int) in sample units. +sample_offsets = [math.modf((float(subpxl_index)/subpxls_per_pixel + 1.0/(2.0*subpxls_per_pixel))*samples_per_pixel) for subpxl_index in range(subpxls_per_pixel)] + +#How many samples to consider to the left and right of the subpxl center. +sample_units_width = 5 + +#The std deviation at sample_units_width. +std_dev_max = 3 + +#The target sum is in some fixed point representation. +#Values larger the 1 in fixed point simulate ink spread. +target_sum = 0x110 + +for sample_offset, sample_align in sample_offsets: + coeffs = [] + coeffs_rounded = [] + + #We start at sample_offset - sample_units_width + current_sample_left = sample_offset - sample_units_width + current_std_dev_left = -std_dev_max + + done = False + while not done: + current_sample_right = math.floor(current_sample_left + 1) + if current_sample_right > sample_offset + sample_units_width: + done = True + current_sample_right = sample_offset + sample_units_width + current_std_dev_right = current_std_dev_left + ((current_sample_right - current_sample_left) / sample_units_width) * std_dev_max + + coverage = withinStdDevRange(current_std_dev_left, current_std_dev_right) + coeffs.append(coverage * target_sum) + coeffs_rounded.append(int(round(coverage * target_sum))) + + current_sample_left = current_sample_right + current_std_dev_left = current_std_dev_right + + # Now we have the numbers we want, but our rounding needs to add up to target_sum. + delta = 0 + coeffs_rounded_sum = sum(coeffs_rounded) + if coeffs_rounded_sum > target_sum: + # The coeffs add up to too much. Subtract 1 from the ones which were rounded up the most. + delta = -1 + + if coeffs_rounded_sum < target_sum: + # The coeffs add up to too little. Add 1 to the ones which were rounded down the most. + delta = 1 + + if delta: + print "Initial sum is 0x%0.2X, adjusting." % (coeffs_rounded_sum,) + coeff_diff = [(coeff_rounded - coeff) * delta + for coeff, coeff_rounded in zip(coeffs, coeffs_rounded)] + + class IndexTracker: + def __init__(self, index, item): + self.index = index + self.item = item + def __lt__(self, other): + return self.item < other.item + def __repr__(self): + return "arr[%d] == %s" % (self.index, repr(self.item)) + + coeff_pkg = [IndexTracker(i, diff) for i, diff in enumerate(coeff_diff)] + coeff_pkg.sort() + + # num_elements_to_force_round had better be < (2 * sample_units_width + 1) or + # * our math was wildy wrong + # * an awful lot of the curve is out side our sample + # either is pretty bad, and probably means the results will not be useful. + num_elements_to_force_round = abs(coeffs_rounded_sum - target_sum) + for i in xrange(num_elements_to_force_round): + print "Adding %d to index %d to force round %f." % (delta, coeff_pkg[i].index, coeffs[coeff_pkg[i].index]) + coeffs_rounded[coeff_pkg[i].index] += delta + + print "Prepending %d 0x00 for allignment." % (sample_align,) + coeffs_rounded_aligned = ([0] * int(sample_align)) + coeffs_rounded + + print ', '.join(["0x%0.2X" % coeff_rounded for coeff_rounded in coeffs_rounded_aligned]) + print sum(coeffs), hex(sum(coeffs_rounded)) + print diff --git a/chromium/third_party/skia/tools/git-skia-verify b/chromium/third_party/skia/tools/git-skia-verify new file mode 100644 index 00000000000..c9a6c9da21c --- /dev/null +++ b/chromium/third_party/skia/tools/git-skia-verify @@ -0,0 +1,95 @@ +#!/bin/sh +# +# Copyright 2012 Intel Inc. +# +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# This script builds and runs GM in current workspace with another Skia +# revision user specifies, and then compares their results. This script is +# useful when developers want to know whether their changes would cause any +# regression. +# +# As the name of this script tells, it only works for git repository. :) +# +# Usage: +# Put this script into where your PATH can find it. +# And then invoke: +# $ git skia-verify [sha1-to-compare-default-is-HEAD^] +# It would delete {before,after,diff} directory under the current directory, +# so be warned! +# After it's done, check out diff/index.html for the possible differences. + + +function say() { + # set color to yellow + tput setaf 3 + echo $1 + tput sgr0 +} + +function warn() { + # set color to red + tput setaf 1 + echo $1 + tput sgr0 +} + +REVISION="HEAD^" + +if [[ $# -eq 1 ]]; +then + REVISION="$1" +fi + +tput clear + +say "Checking sanity..." +git diff --exit-code > /dev/null +if [[ $? -ne 0 ]]; +then + warn "You have uncommitted changes!" + exit 1 +fi +git diff --cached --exit-code > /dev/null +if [[ $? -ne 0 ]]; +then + warn "You have uncommitted changes!" + exit 1 +fi + +say "Preparing Directories..." +rm -rf {before,after,diff} +mkdir {before,after,diff} + +PREVIOUS_BRANCH=`git branch --no-color | grep "^*" | awk '{ print $2}'` + +say "Running GM for current revision..." +./gyp_skia +make BUILDTYPE=Release -j10 +if [[ $? -ne 0 ]]; +then + warn "Failed to compile!" + exit 1 +fi +./out/Release/gm -w after + +say "Running GM for revision $REVISION..." +# we run the test in a detached branch +git checkout --detach "$REVISION" +./gyp_skia +make BUILDTYPE=Release -j10 +if [[ $? -ne 0 ]]; +then + warn "Failed to compile!" + say "Back to original revision..." + git checkout "$PREVIOUS_BRANCH" + exit 1 +fi +./out/Release/gm -w before + +say "Back to original revision..." +git checkout "$PREVIOUS_BRANCH" + +say "Comparing..." +./out/Release/skdiff before after diff diff --git a/chromium/third_party/skia/tools/git-sync-deps b/chromium/third_party/skia/tools/git-sync-deps new file mode 100755 index 00000000000..ee37e631f73 --- /dev/null +++ b/chromium/third_party/skia/tools/git-sync-deps @@ -0,0 +1,205 @@ +#!/usr/bin/python +# Copyright 2014 Google Inc. +# +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +"""Parse a DEPS file and git checkout all of the dependencies. + +Args: + An optional list of deps_os values. + +Environment Variables: + GIT_EXECUTABLE: path to "git" binary; if unset, will look for one of + ['git', 'git.exe', 'git.bat'] in your default path. + + GIT_SYNC_DEPS_PATH: file to get the dependency list from; if unset, + will use the file ../DEPS relative to this script's directory. + + GIT_SYNC_DEPS_QUIET: if set to non-empty string, suppress messages. + +Git Config: + To disable syncing of a single repository: + cd path/to/repository + git config sync-deps.disable true + + To re-enable sync: + cd path/to/repository + git config --unset sync-deps.disable +""" + + +import os +import subprocess +import sys +import threading + +from git_utils import git_executable + + +DEFAULT_DEPS_PATH = os.path.normpath( + os.path.join(os.path.dirname(__file__), os.pardir, 'DEPS')) + + +def usage(deps_file_path = None): + sys.stderr.write( + 'Usage: run to grab dependencies, with optional platform support:\n') + sys.stderr.write(' python %s' % __file__) + if deps_file_path: + for deps_os in parse_file_to_dict(deps_file_path)['deps_os']: + sys.stderr.write(' [%s]' % deps_os) + else: + sys.stderr.write(' [DEPS_OS...]') + sys.stderr.write('\n\n') + sys.stderr.write(__doc__) + + +def git_repository_sync_is_disabled(git, directory): + try: + disable = subprocess.check_output( + [git, 'config', 'sync-deps.disable'], cwd=directory) + return disable.lower().strip() in ['true', '1', 'yes', 'on'] + except subprocess.CalledProcessError: + return False + + +def is_git_toplevel(git, directory): + """Return true iff the directory is the top level of a Git repository. + + Args: + git (string) the git executable + + directory (string) the path into which the repository + is expected to be checked out. + """ + try: + toplevel = subprocess.check_output( + [git, 'rev-parse', '--show-toplevel'], cwd=directory).strip() + return os.path.realpath(directory) == os.path.realpath(toplevel) + except subprocess.CalledProcessError: + return False + + +def git_checkout_to_directory(git, repo, checkoutable, directory, verbose): + """Checkout (and clone if needed) a Git repository. + + Args: + git (string) the git executable + + repo (string) the location of the repository, suitable + for passing to `git clone`. + + checkoutable (string) a tag, branch, or commit, suitable for + passing to `git checkout` + + directory (string) the path into which the repository + should be checked out. + + verbose (boolean) + + Raises an exception if any calls to git fail. + """ + if not os.path.isdir(directory): + subprocess.check_call( + [git, 'clone', '--quiet', repo, directory]) + + if not is_git_toplevel(git, directory): + # if the directory exists, but isn't a git repo, you will modify + # the parent repostory, which isn't what you want. + sys.stdout.write('%s\n IS NOT TOP-LEVEL GIT DIRECTORY.\n' % directory) + return + + # Check to see if this repo is disabled. Quick return. + if git_repository_sync_is_disabled(git, directory): + sys.stdout.write('%s\n SYNC IS DISABLED.\n' % directory) + return + + subprocess.check_call([git, 'fetch', '--quiet'], cwd=directory) + + subprocess.check_call( + [git, 'checkout', '--quiet', checkoutable], cwd=directory) + + if verbose: + sys.stdout.write('%s\n @ %s\n' % (directory, checkoutable)) # Success. + + +def parse_file_to_dict(path): + dictionary = {} + execfile(path, dictionary) + return dictionary + + +class DepsError(Exception): + """Raised if deps_os is a bad key. + """ + pass + + +def git_sync_deps(deps_file_path, deps_os_list, verbose): + """Grab dependencies, with optional platform support. + + Args: + deps_file_path (string) Path to the DEPS file. + + deps_os_list (list of strings) Can be empty list. List of + strings that should each be a key in the deps_os + dictionary in the DEPS file. + + Raises DepsError exception and git Exceptions. + """ + git = git_executable() + assert git + + deps_file_directory = os.path.dirname(deps_file_path) + deps = parse_file_to_dict(deps_file_path) + dependencies = deps['deps'].copy() + for deps_os in deps_os_list: + # Add OS-specific dependencies + if deps_os not in deps['deps_os']: + raise DepsError( + 'Argument "%s" not found within deps_os keys %r' % + (deps_os, deps['deps_os'].keys())) + for dep in deps['deps_os'][deps_os]: + dependencies[dep] = deps['deps_os'][deps_os][dep] + list_of_arg_lists = [] + for directory in dependencies: + if '@' in dependencies[directory]: + repo, checkoutable = dependencies[directory].split('@', 1) + else: + repo, checkoutable = dependencies[directory], 'origin/master' + + relative_directory = os.path.join(deps_file_directory, directory) + + list_of_arg_lists.append( + (git, repo, checkoutable, relative_directory, verbose)) + + multithread(git_checkout_to_directory, list_of_arg_lists) + + +def multithread(function, list_of_arg_lists): + # for args in list_of_arg_lists: + # function(*args) + # return + threads = [] + for args in list_of_arg_lists: + thread = threading.Thread(None, function, None, args) + thread.start() + threads.append(thread) + for thread in threads: + thread.join() + + +def main(argv): + deps_file_path = os.environ.get('GIT_SYNC_DEPS_PATH', DEFAULT_DEPS_PATH) + verbose = not bool(os.environ.get('GIT_SYNC_DEPS_QUIET', False)) + try: + git_sync_deps(deps_file_path, argv, verbose) + return 0 + except DepsError: + usage(deps_file_path) + return 1 + + +if __name__ == '__main__': + exit(main(sys.argv[1:])) diff --git a/chromium/third_party/skia/tools/git_utils.py b/chromium/third_party/skia/tools/git_utils.py new file mode 100644 index 00000000000..a35c85e20e1 --- /dev/null +++ b/chromium/third_party/skia/tools/git_utils.py @@ -0,0 +1,168 @@ +# Copyright 2014 Google Inc. +# +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Module to host the ChangeGitBranch class and test_git_executable function. +""" + +import os +import subprocess + +import misc_utils + + +class ChangeGitBranch(object): + """Class to manage git branches. + + This class allows one to create a new branch in a repository based + off of a given commit, and restore the original tree state. + + Assumes current working directory is a git repository. + + Example: + with ChangeGitBranch(): + edit_files(files) + git_add(files) + git_commit() + git_format_patch('HEAD~') + # At this point, the repository is returned to its original + # state. + + Constructor Args: + branch_name: (string) if not None, the name of the branch to + use. If None, then use a temporary branch that will be + deleted. If the branch already exists, then a different + branch name will be created. Use git_branch_name() to + find the actual branch name used. + upstream_branch: (string) if not None, the name of the branch or + commit to branch from. If None, then use origin/master + verbose: (boolean) if true, makes debugging easier. + + Raises: + OSError: the git executable disappeared. + subprocess.CalledProcessError: git returned unexpected status. + Exception: if the given branch name exists, or if the repository + isn't clean on exit, or git can't be found. + """ + # pylint: disable=I0011,R0903,R0902 + + def __init__(self, + branch_name=None, + upstream_branch=None, + verbose=False): + # pylint: disable=I0011,R0913 + if branch_name: + self._branch_name = branch_name + self._delete_branch = False + else: + self._branch_name = 'ChangeGitBranchTempBranch' + self._delete_branch = True + + if upstream_branch: + self._upstream_branch = upstream_branch + else: + self._upstream_branch = 'origin/master' + + self._git = git_executable() + if not self._git: + raise Exception('Git can\'t be found.') + + self._stash = None + self._original_branch = None + self._vsp = misc_utils.VerboseSubprocess(verbose) + + def _has_git_diff(self): + """Return true iff repository has uncommited changes.""" + return bool(self._vsp.call([self._git, 'diff', '--quiet', 'HEAD'])) + + def _branch_exists(self, branch): + """Return true iff branch exists.""" + return 0 == self._vsp.call([self._git, 'show-ref', '--quiet', branch]) + + def __enter__(self): + git, vsp = self._git, self._vsp + + if self._branch_exists(self._branch_name): + i, branch_name = 0, self._branch_name + while self._branch_exists(branch_name): + i += 1 + branch_name = '%s_%03d' % (self._branch_name, i) + self._branch_name = branch_name + + self._stash = self._has_git_diff() + if self._stash: + vsp.check_call([git, 'stash', 'save']) + self._original_branch = git_branch_name(vsp.verbose) + vsp.check_call( + [git, 'checkout', '-q', '-b', + self._branch_name, self._upstream_branch]) + + def __exit__(self, etype, value, traceback): + git, vsp = self._git, self._vsp + + if self._has_git_diff(): + status = vsp.check_output([git, 'status', '-s']) + raise Exception('git checkout not clean:\n%s' % status) + vsp.check_call([git, 'checkout', '-q', self._original_branch]) + if self._stash: + vsp.check_call([git, 'stash', 'pop']) + if self._delete_branch: + assert self._original_branch != self._branch_name + vsp.check_call([git, 'branch', '-D', self._branch_name]) + + +def git_branch_name(verbose=False): + """Return a description of the current branch. + + Args: + verbose: (boolean) makes debugging easier + + Returns: + A string suitable for passing to `git checkout` later. + """ + git = git_executable() + vsp = misc_utils.VerboseSubprocess(verbose) + try: + full_branch = vsp.strip_output([git, 'symbolic-ref', 'HEAD']) + return full_branch.split('/')[-1] + except (subprocess.CalledProcessError,): + # "fatal: ref HEAD is not a symbolic ref" + return vsp.strip_output([git, 'rev-parse', 'HEAD']) + + +def test_git_executable(git): + """Test the git executable. + + Args: + git: git executable path. + Returns: + True if test is successful. + """ + with open(os.devnull, 'w') as devnull: + try: + subprocess.call([git, '--version'], stdout=devnull) + except (OSError,): + return False + return True + + +def git_executable(): + """Find the git executable. + + If the GIT_EXECUTABLE environment variable is set, that will + override whatever is found in the PATH. + + If no suitable executable is found, return None + + Returns: + A string suiable for passing to subprocess functions, or None. + """ + env_git = os.environ.get('GIT_EXECUTABLE') + if env_git and test_git_executable(env_git): + return env_git + for git in ('git', 'git.exe', 'git.bat'): + if test_git_executable(git): + return git + return None + diff --git a/chromium/third_party/skia/tools/gpuveto.cpp b/chromium/third_party/skia/tools/gpuveto.cpp new file mode 100644 index 00000000000..68f343b6507 --- /dev/null +++ b/chromium/third_party/skia/tools/gpuveto.cpp @@ -0,0 +1,78 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "LazyDecodeBitmap.h" +#include "SkCommandLineFlags.h" +#include "SkPicture.h" +#include "SkPictureRecorder.h" +#include "SkStream.h" + +DEFINE_string2(readFile, r, "", "skp file to process."); +DEFINE_bool2(quiet, q, false, "quiet"); + +// This tool just loads a single skp, replays into a new SkPicture (to +// regenerate the GPU-specific tracking information) and reports +// the value of the suitableForGpuRasterization method. +// Return codes: +static const int kSuccess = 0; +static const int kError = 1; + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { +#if SK_SUPPORT_GPU + SkCommandLineFlags::SetUsage("Reports on an skp file's suitability for GPU rasterization"); + SkCommandLineFlags::Parse(argc, argv); + + if (FLAGS_readFile.count() != 1) { + if (!FLAGS_quiet) { + SkDebugf("Missing input file\n"); + } + return kError; + } + + SkFILEStream inputStream(FLAGS_readFile[0]); + if (!inputStream.isValid()) { + if (!FLAGS_quiet) { + SkDebugf("Couldn't open file\n"); + } + return kError; + } + + SkPicture::InstallPixelRefProc proc = &sk_tools::LazyDecodeBitmap; + + SkAutoTUnref<SkPicture> picture(SkPicture::CreateFromStream(&inputStream, proc)); + if (NULL == picture.get()) { + if (!FLAGS_quiet) { + SkDebugf("Could not read the SkPicture\n"); + } + return kError; + } + + // The SkPicture tracking information is only generated during recording + // an isn't serialized. Replay the picture to regenerated the tracking data. + SkPictureRecorder recorder; + picture->draw(recorder.beginRecording(picture->width(), picture->height(), NULL, 0)); + SkAutoTUnref<SkPicture> recorded(recorder.endRecording()); + + if (recorded->suitableForGpuRasterization(NULL)) { + SkDebugf("suitable\n"); + } else { + SkDebugf("unsuitable\n"); + } + + return kSuccess; +#else + SkDebugf("gpuveto is only useful when GPU rendering is enabled\n"); + return kError; +#endif +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/image_expectations.cpp b/chromium/third_party/skia/tools/image_expectations.cpp new file mode 100644 index 00000000000..ac232e9f30d --- /dev/null +++ b/chromium/third_party/skia/tools/image_expectations.cpp @@ -0,0 +1,232 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkBitmap.h" +#include "SkBitmapHasher.h" +#include "SkData.h" +#include "SkJSONCPP.h" +#include "SkOSFile.h" +#include "SkStream.h" +#include "SkTypes.h" + +#include "image_expectations.h" + +/* + * TODO(epoger): Make constant strings consistent instead of mixing hypenated and camel-caps. + * + * TODO(epoger): Similar constants are already maintained in 2 other places: + * gm/gm_json.py and gm/gm_expectations.cpp. We shouldn't add yet a third place. + * Figure out a way to share the definitions instead. + * + * Note that, as of https://codereview.chromium.org/226293002 , the JSON + * schema used here has started to differ from the one in gm_expectations.cpp . + * TODO(epoger): Consider getting GM and render_pictures to use the same JSON + * output module. + */ +const static char kJsonKey_ActualResults[] = "actual-results"; +const static char kJsonKey_ExpectedResults[] = "expected-results"; +const static char kJsonKey_Header[] = "header"; +const static char kJsonKey_Header_Type[] = "type"; +const static char kJsonKey_Header_Revision[] = "revision"; +const static char kJsonKey_Image_ChecksumAlgorithm[] = "checksumAlgorithm"; +const static char kJsonKey_Image_ChecksumValue[] = "checksumValue"; +const static char kJsonKey_Image_ComparisonResult[] = "comparisonResult"; +const static char kJsonKey_Image_Filepath[] = "filepath"; +const static char kJsonKey_Image_IgnoreFailure[] = "ignoreFailure"; +const static char kJsonKey_Source_TiledImages[] = "tiled-images"; +const static char kJsonKey_Source_WholeImage[] = "whole-image"; +// Values (not keys) that are written out by this JSON generator +const static char kJsonValue_Header_Type[] = "ChecksummedImages"; +const static int kJsonValue_Header_Revision = 1; +const static char kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5[] = "bitmap-64bitMD5"; +const static char kJsonValue_Image_ComparisonResult_Failed[] = "failed"; +const static char kJsonValue_Image_ComparisonResult_FailureIgnored[] = "failure-ignored"; +const static char kJsonValue_Image_ComparisonResult_NoComparison[] = "no-comparison"; +const static char kJsonValue_Image_ComparisonResult_Succeeded[] = "succeeded"; + +namespace sk_tools { + + // ImageDigest class... + + ImageDigest::ImageDigest(const SkBitmap &bitmap) { + if (!SkBitmapHasher::ComputeDigest(bitmap, &fHashValue)) { + SkFAIL("unable to compute image digest"); + } + } + + ImageDigest::ImageDigest(const SkString &hashType, uint64_t hashValue) { + if (!hashType.equals(kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5)) { + SkFAIL((SkString("unsupported hashType ")+=hashType).c_str()); + } else { + fHashValue = hashValue; + } + } + + SkString ImageDigest::getHashType() const { + // TODO(epoger): The current implementation assumes that the + // result digest is always of type kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5 . + return SkString(kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5); + } + + uint64_t ImageDigest::getHashValue() const { + return fHashValue; + } + + // BitmapAndDigest class... + + BitmapAndDigest::BitmapAndDigest(const SkBitmap &bitmap) : fBitmap(bitmap) { + } + + const ImageDigest *BitmapAndDigest::getImageDigestPtr() { + if (NULL == fImageDigestRef.get()) { + fImageDigestRef.reset(SkNEW_ARGS(ImageDigest, (fBitmap))); + } + return fImageDigestRef.get(); + } + + const SkBitmap *BitmapAndDigest::getBitmapPtr() const { + return &fBitmap; + } + + // ImageResultsAndExpectations class... + + bool ImageResultsAndExpectations::readExpectationsFile(const char *jsonPath) { + if (NULL == jsonPath) { + SkDebugf("JSON expectations filename not specified\n"); + return false; + } + SkFILE* filePtr = sk_fopen(jsonPath, kRead_SkFILE_Flag); + if (NULL == filePtr) { + SkDebugf("JSON expectations file '%s' does not exist\n", jsonPath); + return false; + } + size_t size = sk_fgetsize(filePtr); + if (0 == size) { + SkDebugf("JSON expectations file '%s' is empty, so no expectations\n", jsonPath); + sk_fclose(filePtr); + fExpectedResults.clear(); + return true; + } + bool parsedJson = Parse(filePtr, &fExpectedJsonRoot); + sk_fclose(filePtr); + if (!parsedJson) { + SkDebugf("Failed to parse JSON expectations file '%s'\n", jsonPath); + return false; + } + Json::Value header = fExpectedJsonRoot[kJsonKey_Header]; + Json::Value headerType = header[kJsonKey_Header_Type]; + Json::Value headerRevision = header[kJsonKey_Header_Revision]; + if (strcmp(headerType.asCString(), kJsonValue_Header_Type)) { + SkDebugf("JSON expectations file '%s': expected headerType '%s', found '%s'\n", + jsonPath, kJsonValue_Header_Type, headerType.asCString()); + return false; + } + if (headerRevision.asInt() != kJsonValue_Header_Revision) { + SkDebugf("JSON expectations file '%s': expected headerRevision %d, found %d\n", + jsonPath, kJsonValue_Header_Revision, headerRevision.asInt()); + return false; + } + fExpectedResults = fExpectedJsonRoot[kJsonKey_ExpectedResults]; + return true; + } + + void ImageResultsAndExpectations::add(const char *sourceName, const char *fileName, + const ImageDigest &digest, const int *tileNumber) { + // Get expectation, if any. + Json::Value expectedImage; + if (!fExpectedResults.isNull()) { + if (NULL == tileNumber) { + expectedImage = fExpectedResults[sourceName][kJsonKey_Source_WholeImage]; + } else { + expectedImage = fExpectedResults[sourceName][kJsonKey_Source_TiledImages] + [*tileNumber]; + } + } + + // Fill in info about the actual result itself. + Json::Value actualChecksumAlgorithm = digest.getHashType().c_str(); + Json::Value actualChecksumValue = Json::UInt64(digest.getHashValue()); + Json::Value actualImage; + actualImage[kJsonKey_Image_ChecksumAlgorithm] = actualChecksumAlgorithm; + actualImage[kJsonKey_Image_ChecksumValue] = actualChecksumValue; + actualImage[kJsonKey_Image_Filepath] = fileName; + + // Compare against expectedImage to fill in comparisonResult. + Json::Value comparisonResult = kJsonValue_Image_ComparisonResult_NoComparison; + if (!expectedImage.isNull()) { + if ((actualChecksumAlgorithm == expectedImage[kJsonKey_Image_ChecksumAlgorithm]) && + (actualChecksumValue == expectedImage[kJsonKey_Image_ChecksumValue])) { + comparisonResult = kJsonValue_Image_ComparisonResult_Succeeded; + } else if (expectedImage[kJsonKey_Image_IgnoreFailure] == true) { + comparisonResult = kJsonValue_Image_ComparisonResult_FailureIgnored; + } else { + comparisonResult = kJsonValue_Image_ComparisonResult_Failed; + } + } + actualImage[kJsonKey_Image_ComparisonResult] = comparisonResult; + + // Add this actual result to our collection. + if (NULL == tileNumber) { + fActualResults[sourceName][kJsonKey_Source_WholeImage] = actualImage; + } else { + fActualResults[sourceName][kJsonKey_Source_TiledImages][*tileNumber] = actualImage; + } + } + + bool ImageResultsAndExpectations::matchesExpectation(const char *sourceName, + const ImageDigest &digest, + const int *tileNumber) { + if (fExpectedResults.isNull()) { + return false; + } + + Json::Value expectedImage; + if (NULL == tileNumber) { + expectedImage = fExpectedResults[sourceName][kJsonKey_Source_WholeImage]; + } else { + expectedImage = fExpectedResults[sourceName][kJsonKey_Source_TiledImages][*tileNumber]; + } + if (expectedImage.isNull()) { + return false; + } + + Json::Value actualChecksumAlgorithm = digest.getHashType().c_str(); + Json::Value actualChecksumValue = Json::UInt64(digest.getHashValue()); + return ((actualChecksumAlgorithm == expectedImage[kJsonKey_Image_ChecksumAlgorithm]) && + (actualChecksumValue == expectedImage[kJsonKey_Image_ChecksumValue])); + } + + void ImageResultsAndExpectations::writeToFile(const char *filename) const { + Json::Value header; + header[kJsonKey_Header_Type] = kJsonValue_Header_Type; + header[kJsonKey_Header_Revision] = kJsonValue_Header_Revision; + Json::Value root; + root[kJsonKey_Header] = header; + root[kJsonKey_ActualResults] = fActualResults; + std::string jsonStdString = root.toStyledString(); + SkFILEWStream stream(filename); + stream.write(jsonStdString.c_str(), jsonStdString.length()); + } + + /*static*/ bool ImageResultsAndExpectations::Parse(SkFILE *filePtr, + Json::Value *jsonRoot) { + SkAutoDataUnref dataRef(SkData::NewFromFILE(filePtr)); + if (NULL == dataRef.get()) { + return false; + } + + const char *bytes = reinterpret_cast<const char *>(dataRef.get()->data()); + size_t size = dataRef.get()->size(); + Json::Reader reader; + if (!reader.parse(bytes, bytes+size, *jsonRoot)) { + return false; + } + + return true; + } + +} // namespace sk_tools diff --git a/chromium/third_party/skia/tools/image_expectations.h b/chromium/third_party/skia/tools/image_expectations.h new file mode 100644 index 00000000000..a24334e60d3 --- /dev/null +++ b/chromium/third_party/skia/tools/image_expectations.h @@ -0,0 +1,149 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef image_expectations_DEFINED +#define image_expectations_DEFINED + +#include "SkBitmap.h" +#include "SkJSONCPP.h" +#include "SkOSFile.h" +#include "SkRefCnt.h" + +namespace sk_tools { + + /** + * The digest of an image (either an image we have generated locally, or an image expectation). + * + * Currently, this is always a uint64_t hash digest of an SkBitmap. + */ + class ImageDigest : public SkRefCnt { + public: + /** + * Create an ImageDigest of a bitmap. + * + * Note that this is an expensive operation, because it has to examine all pixels in + * the bitmap. You may wish to consider using the BitmapAndDigest class, which will + * compute the ImageDigest lazily. + * + * @param bitmap image to get the digest of + */ + explicit ImageDigest(const SkBitmap &bitmap); + + /** + * Create an ImageDigest using a hashType/hashValue pair. + * + * @param hashType the algorithm used to generate the hash; for now, only + * kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5 is allowed. + * @param hashValue the value generated by the hash algorithm for a particular image. + */ + explicit ImageDigest(const SkString &hashType, uint64_t hashValue); + + /** + * Returns the hash digest type as an SkString. + * + * For now, this always returns kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5 . + */ + SkString getHashType() const; + + /** + * Returns the hash digest value as a uint64_t. + */ + uint64_t getHashValue() const; + + private: + uint64_t fHashValue; + }; + + /** + * Container that holds a reference to an SkBitmap and computes its ImageDigest lazily. + * + * Computing the ImageDigest can be expensive, so this can help you postpone (or maybe even + * avoid) that work. + */ + class BitmapAndDigest { + public: + explicit BitmapAndDigest(const SkBitmap &bitmap); + + const ImageDigest *getImageDigestPtr(); + const SkBitmap *getBitmapPtr() const; + private: + const SkBitmap fBitmap; + SkAutoTUnref<ImageDigest> fImageDigestRef; + }; + + /** + * Collects ImageDigests of actually rendered images, perhaps comparing to expectations. + */ + class ImageResultsAndExpectations { + public: + /** + * Adds expectations from a JSON file, returning true if successful. + * + * If the file exists but is empty, it succeeds, and there will be no expectations. + * If the file does not exist, this will fail. + * + * Reasoning: + * Generating expectations the first time can be a tricky chicken-and-egg + * proposition. "I need actual results to turn into expectations... but the only + * way to get actual results is to run the tool, and the tool won't run without + * expectations!" + * We could make the tool run even if there is no expectations file at all, but it's + * better for the tool to fail if the expectations file is not found--that will tell us + * quickly if files are not being copied around as they should be. + * Creating an empty file is an easy way to break the chicken-and-egg cycle and generate + * the first real expectations. + */ + bool readExpectationsFile(const char *jsonPath); + + /** + * Adds this image to the summary of results. + * + * @param sourceName name of the source file that generated this result + * @param fileName relative path to the image output file on local disk + * @param digest description of the image's contents + * @param tileNumber if not NULL, pointer to tile number + */ + void add(const char *sourceName, const char *fileName, const ImageDigest &digest, + const int *tileNumber=NULL); + + /** + * Returns true if this test result matches its expectations. + * If there are no expectations for this test result, this will return false. + * + * @param sourceName name of the source file that generated this result + * @param digest description of the image's contents + * @param tileNumber if not NULL, pointer to tile number + */ + bool matchesExpectation(const char *sourceName, const ImageDigest &digest, + const int *tileNumber=NULL); + + /** + * Writes the summary (as constructed so far) to a file. + * + * @param filename path to write the summary to + */ + void writeToFile(const char *filename) const; + + private: + + /** + * Read the file contents from filePtr and parse them into jsonRoot. + * + * It is up to the caller to close filePtr after this is done. + * + * Returns true if successful. + */ + static bool Parse(SkFILE* filePtr, Json::Value *jsonRoot); + + Json::Value fActualResults; + Json::Value fExpectedJsonRoot; + Json::Value fExpectedResults; + }; + +} // namespace sk_tools + +#endif // image_expectations_DEFINED diff --git a/chromium/third_party/skia/tools/install_dependencies.sh b/chromium/third_party/skia/tools/install_dependencies.sh new file mode 100755 index 00000000000..5769602115a --- /dev/null +++ b/chromium/third_party/skia/tools/install_dependencies.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# Copyright 2014 Google Inc. +# +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# install_dependencies.sh will install system-specific Skia +# dependencies using your system's package manager. If your system is +# not supported, add logic here to support it. + +set -e + +if command -v lsb_release > /dev/null ; then + case $(lsb_release -i -s) in + Ubuntu) + sudo apt-get install \ + build-essential \ + libpoppler-cpp-dev \ + libfreetype6-dev \ + libfontconfig-dev \ + libpng12-dev \ + libgif-dev \ + libqt4-dev \ + clang + if [ $(lsb_release -r -s) = '14.04' ] ; then + sudo apt-get install \ + ninja-build + fi + exit + ;; + esac +fi + +echo 'unknown system' +exit 1 + diff --git a/chromium/third_party/skia/tools/jsondiff.py b/chromium/third_party/skia/tools/jsondiff.py new file mode 100755 index 00000000000..050a177b002 --- /dev/null +++ b/chromium/third_party/skia/tools/jsondiff.py @@ -0,0 +1,201 @@ +#!/usr/bin/python + +''' +Copyright 2013 Google Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +''' + +''' +Gathers diffs between 2 JSON expectations files, or between actual and +expected results within a single JSON actual-results file, +and generates an old-vs-new diff dictionary. + +TODO(epoger): Fix indentation in this file (2-space indents, not 4-space). +''' + +# System-level imports +import argparse +import json +import os +import sys +import urllib2 + +# Imports from within Skia +# +# We need to add the 'gm' directory, so that we can import gm_json.py within +# that directory. That script allows us to parse the actual-results.json file +# written out by the GM tool. +# Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end* +# so any dirs that are already in the PYTHONPATH will be preferred. +# +# This assumes that the 'gm' directory has been checked out as a sibling of +# the 'tools' directory containing this script, which will be the case if +# 'trunk' was checked out as a single unit. +GM_DIRECTORY = os.path.realpath( + os.path.join(os.path.dirname(os.path.dirname(__file__)), 'gm')) +if GM_DIRECTORY not in sys.path: + sys.path.append(GM_DIRECTORY) +import gm_json + + +# Object that generates diffs between two JSON gm result files. +class GMDiffer(object): + + def __init__(self): + pass + + def _GetFileContentsAsString(self, filepath): + """Returns the full contents of a file, as a single string. + If the filename looks like a URL, download its contents. + If the filename is None, return None.""" + if filepath is None: + return None + elif filepath.startswith('http:') or filepath.startswith('https:'): + return urllib2.urlopen(filepath).read() + else: + return open(filepath, 'r').read() + + def _GetExpectedResults(self, contents): + """Returns the dictionary of expected results from a JSON string, + in this form: + + { + 'test1' : 14760033689012826769, + 'test2' : 9151974350149210736, + ... + } + + We make these simplifying assumptions: + 1. Each test has either 0 or 1 allowed results. + 2. All expectations are of type JSONKEY_HASHTYPE_BITMAP_64BITMD5. + + Any tests which violate those assumptions will cause an exception to + be raised. + + Any tests for which we have no expectations will be left out of the + returned dictionary. + """ + result_dict = {} + json_dict = gm_json.LoadFromString(contents) + all_expectations = json_dict[gm_json.JSONKEY_EXPECTEDRESULTS] + + # Prevent https://code.google.com/p/skia/issues/detail?id=1588 + # ('svndiff.py: 'NoneType' object has no attribute 'keys'') + if not all_expectations: + return result_dict + + for test_name in all_expectations.keys(): + test_expectations = all_expectations[test_name] + allowed_digests = test_expectations[ + gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS] + if allowed_digests: + num_allowed_digests = len(allowed_digests) + if num_allowed_digests > 1: + raise ValueError( + 'test %s has %d allowed digests' % ( + test_name, num_allowed_digests)) + digest_pair = allowed_digests[0] + if digest_pair[0] != gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5: + raise ValueError( + 'test %s has unsupported hashtype %s' % ( + test_name, digest_pair[0])) + result_dict[test_name] = digest_pair[1] + return result_dict + + def _GetActualResults(self, contents): + """Returns the dictionary of actual results from a JSON string, + in this form: + + { + 'test1' : 14760033689012826769, + 'test2' : 9151974350149210736, + ... + } + + We make these simplifying assumptions: + 1. All results are of type JSONKEY_HASHTYPE_BITMAP_64BITMD5. + + Any tests which violate those assumptions will cause an exception to + be raised. + + Any tests for which we have no actual results will be left out of the + returned dictionary. + """ + result_dict = {} + json_dict = gm_json.LoadFromString(contents) + all_result_types = json_dict[gm_json.JSONKEY_ACTUALRESULTS] + for result_type in all_result_types.keys(): + results_of_this_type = all_result_types[result_type] + if results_of_this_type: + for test_name in results_of_this_type.keys(): + digest_pair = results_of_this_type[test_name] + if digest_pair[0] != gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5: + raise ValueError( + 'test %s has unsupported hashtype %s' % ( + test_name, digest_pair[0])) + result_dict[test_name] = digest_pair[1] + return result_dict + + def _DictionaryDiff(self, old_dict, new_dict): + """Generate a dictionary showing the diffs between old_dict and new_dict. + Any entries which are identical across them will be left out.""" + diff_dict = {} + all_keys = set(old_dict.keys() + new_dict.keys()) + for key in all_keys: + if old_dict.get(key) != new_dict.get(key): + new_entry = {} + new_entry['old'] = old_dict.get(key) + new_entry['new'] = new_dict.get(key) + diff_dict[key] = new_entry + return diff_dict + + def GenerateDiffDict(self, oldfile, newfile=None): + """Generate a dictionary showing the diffs: + old = expectations within oldfile + new = expectations within newfile + + If newfile is not specified, then 'new' is the actual results within + oldfile. + """ + return self.GenerateDiffDictFromStrings(self._GetFileContentsAsString(oldfile), + self._GetFileContentsAsString(newfile)) + + def GenerateDiffDictFromStrings(self, oldjson, newjson=None): + """Generate a dictionary showing the diffs: + old = expectations within oldjson + new = expectations within newjson + + If newfile is not specified, then 'new' is the actual results within + oldfile. + """ + old_results = self._GetExpectedResults(oldjson) + if newjson: + new_results = self._GetExpectedResults(newjson) + else: + new_results = self._GetActualResults(oldjson) + return self._DictionaryDiff(old_results, new_results) + + +def _Main(): + parser = argparse.ArgumentParser() + parser.add_argument( + 'old', + help='Path to JSON file whose expectations to display on ' + + 'the "old" side of the diff. This can be a filepath on ' + + 'local storage, or a URL.') + parser.add_argument( + 'new', nargs='?', + help='Path to JSON file whose expectations to display on ' + + 'the "new" side of the diff; if not specified, uses the ' + + 'ACTUAL results from the "old" JSON file. This can be a ' + + 'filepath on local storage, or a URL.') + args = parser.parse_args() + differ = GMDiffer() + diffs = differ.GenerateDiffDict(oldfile=args.old, newfile=args.new) + json.dump(diffs, sys.stdout, sort_keys=True, indent=2) + + +if __name__ == '__main__': + _Main() diff --git a/chromium/third_party/skia/tools/lsan.supp b/chromium/third_party/skia/tools/lsan.supp new file mode 100644 index 00000000000..6a274c02027 --- /dev/null +++ b/chromium/third_party/skia/tools/lsan.supp @@ -0,0 +1,16 @@ +# Supressions for LSAN. +# tools/xsan_build address -C out/Debug +# ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=suppressions=tools/lsan.supp out/Debug/$FOO + +# Ignore fontconfig leaks. +leak:FcFontSet +leak:FcPatternObject + +# It'd be really nice to supress these leaks in the Nvidia driver, but I can't figure it out. +# Direct leak of 18072 byte(s) in 3 object(s) allocated from: +# #0 0x5ebb59 in calloc ~/llvm-3.4/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:90 +# #1 0x7f66af293b1e (/usr/lib/nvidia-current/libGL.so.1+0xbcb1e) + +# Skia leaks +leak:SkRTConf +leak:SkFontMgr diff --git a/chromium/third_party/skia/tools/lua/agg_dash.lua b/chromium/third_party/skia/tools/lua/agg_dash.lua new file mode 100755 index 00000000000..37dffe1dde0 --- /dev/null +++ b/chromium/third_party/skia/tools/lua/agg_dash.lua @@ -0,0 +1,29 @@ +--[[ + This file is used as the aggregator file when using telemetry for + scrape_dashing_full.lua +]] + +dashCount = 0 +dashTable = {} + +function increment_inner(table, key, value) + table[key] = (table[key] or 0) + value +end + +function increment(table, tableKey, key, value) + if (table[tableKey] == nil) then + table[tableKey] = {} + end + increment_inner(table[tableKey], key, value) +end + +dofile("/tmp/lua-output") + +io.write("Total dashed effects is: ", dashCount, "\n") +for k1, v1 in next, dashTable do + io.write("\nTable: ", k1, "\n") + for k, v in next, v1 do + io.write("\"", k, "\": ", v, "\n") + end +end + diff --git a/chromium/third_party/skia/tools/lua/bbh_filter.lua b/chromium/third_party/skia/tools/lua/bbh_filter.lua new file mode 100644 index 00000000000..73b530c7c75 --- /dev/null +++ b/chromium/third_party/skia/tools/lua/bbh_filter.lua @@ -0,0 +1,148 @@ +-- bbh_filter.lua +-- +-- This script outputs info about 'interesting' skp files, +-- where the definition of 'interesting' changes but is roughly: +-- "Interesting for bounding box hierarchy benchmarks." +-- +-- Currently, the approach is to output, in equal ammounts, the names of the files that +-- have most commands, and the names of the files that use the least popular commands. + +function count_entries(table) + local count = 0 + for _,_ in pairs(table) do + count = count + 1 + end + return count +end + +verbCounts = {} + +function reset_current() + -- Data about the skp in transit + currentInfo = { + fileName = '', + verbs = {}, + numOps = 0 + } +end +reset_current() + +numOutputFiles = 10 -- This is per measure. +globalInfo = {} -- Saves currentInfo for each file to be used at the end. +output = {} -- Stores {fileName, {verb, count}} tables. + +function tostr(t) + local str = "" + for k, v in next, t do + if #str > 0 then + str = str .. ", " + end + if type(k) == "number" then + str = str .. "[" .. k .. "] = " + else + str = str .. tostring(k) .. " = " + end + if type(v) == "table" then + str = str .. "{ " .. tostr(v) .. " }" + else + str = str .. tostring(v) + end + end + return str +end + +function sk_scrape_startcanvas(c, fileName) end + +function sk_scrape_endcanvas(c, fileName) + globalInfo[fileName] = currentInfo + globalInfo[fileName].fileName = fileName + reset_current() +end + +function sk_scrape_accumulate(t) + -- dump the params in t, specifically showing the verb first, which we + -- then nil out so it doesn't appear in tostr() + -- + verbCounts[t.verb] = (verbCounts[t.verb] or 0) + 1 + currentInfo.verbs[t.verb] = (currentInfo.verbs[t.verb] or 0) + 1 + currentInfo.numOps = currentInfo.numOps + 1 + + t.verb = nil +end + +function sk_scrape_summarize() + verbWeights = {} -- {verb, weight}, where 0 < weight <= 1 + + meta = {} + for k,v in pairs(verbCounts) do + table.insert(meta, {key=k, value=v}) + end + table.sort(meta, function (a,b) return a.value > b.value; end) + maxValue = meta[1].value + io.write("-- ==================\n") + io.write("------------------------------------------------------------------ \n") + io.write("-- Command\t\t\tNumber of calls\t\tPopularity\n") + io.write("------------------------------------------------------------------ \n") + for k, v in pairs(meta) do + verbWeights[v.key] = v.value / maxValue + + -- Poor man's formatting: + local padding = "\t\t\t" + if (#v.key + 3) < 8 then + padding = "\t\t\t\t" + end + if (#v.key + 3) >= 16 then + padding = "\t\t" + end + + io.write ("-- ",v.key, padding, v.value, '\t\t\t', verbWeights[v.key], "\n") + end + + meta = {} + function calculate_weight(verbs) + local weight = 0 + for name, count in pairs(verbs) do + weight = weight + (1 / verbWeights[name]) * count + end + return weight + end + for n, info in pairs(globalInfo) do + table.insert(meta, info) + end + + local visitedFiles = {} + + -- Prints out information in lua readable format + function output_with_metric(metric_func, description, numOutputFiles) + table.sort(meta, metric_func) + print(description) + local iter = 0 + for i, t in pairs(meta) do + if not visitedFiles[t.fileName] then + visitedFiles[t.fileName] = true + io.write ("{\nname = \"", t.fileName, "\", \nverbs = {\n") + for verb,count in pairs(globalInfo[t.fileName].verbs) do + io.write(' ', verb, " = ", count, ",\n") + end + io.write("}\n},\n") + + iter = iter + 1 + if iter >= numOutputFiles then + break + end + end + end + end + + output_with_metric( + function(a, b) return calculate_weight(a.verbs) > calculate_weight(b.verbs); end, + "\n-- ================== skps with calling unpopular commands.", 10) + output_with_metric( + function(a, b) return a.numOps > b.numOps; end, + "\n-- ================== skps with the most calls.", 50) + + local count = count_entries(visitedFiles) + + print ("-- Spat", count, "files") +end + diff --git a/chromium/third_party/skia/tools/lua/bitmap_statistics.lua b/chromium/third_party/skia/tools/lua/bitmap_statistics.lua new file mode 100644 index 00000000000..4ac8d9385fd --- /dev/null +++ b/chromium/third_party/skia/tools/lua/bitmap_statistics.lua @@ -0,0 +1,59 @@ +function string.startsWith(String,Start) + return string.sub(String,1,string.len(Start))==Start +end + +function string.endsWith(String,End) + return End=='' or string.sub(String,-string.len(End))==End +end + +local canvas = nil +local num_perspective_bitmaps = 0 +local num_affine_bitmaps = 0 +local num_scaled_bitmaps = 0 +local num_translated_bitmaps = 0 +local num_identity_bitmaps = 0 +local num_scaled_up = 0 +local num_scaled_down = 0 + +function sk_scrape_startcanvas(c, fileName) + canvas = c +end + +function sk_scrape_endcanvas(c, fileName) + canvas = nil +end + +function sk_scrape_accumulate(t) + -- dump the params in t, specifically showing the verb first, which we + -- then nil out so it doesn't appear in tostr() + if (string.startsWith(t.verb,"drawBitmap")) then + matrix = canvas:getTotalMatrix() + matrixType = matrix:getType() + if matrixType.perspective then + num_perspective_bitmaps = num_perspective_bitmaps + 1 + elseif matrixType.affine then + num_affine_bitmaps = num_affine_bitmaps + 1 + elseif matrixType.scale then + num_scaled_bitmaps = num_scaled_bitmaps + 1 + if matrix:getScaleX() > 1 or matrix:getScaleY() > 1 then + num_scaled_up = num_scaled_up + 1 + else + num_scaled_down = num_scaled_down + 1 + end + elseif matrixType.translate then + num_translated_bitmaps = num_translated_bitmaps + 1 + else + num_identity_bitmaps = num_identity_bitmaps + 1 + end + end +end + +function sk_scrape_summarize() + io.write( "identity = ", num_identity_bitmaps, + ", translated = ", num_translated_bitmaps, + ", scaled = ", num_scaled_bitmaps, " (up = ", num_scaled_up, "; down = ", num_scaled_down, ")", + ", affine = ", num_affine_bitmaps, + ", perspective = ", num_perspective_bitmaps, + "\n") +end + diff --git a/chromium/third_party/skia/tools/lua/chars-vs-glyphs.lua b/chromium/third_party/skia/tools/lua/chars-vs-glyphs.lua new file mode 100644 index 00000000000..4098387ec12 --- /dev/null +++ b/chromium/third_party/skia/tools/lua/chars-vs-glyphs.lua @@ -0,0 +1,35 @@ +local canvas + +function sk_scrape_startcanvas(c, fileName) + canvas = c +end + +function sk_scrape_endcanvas(c, fileName) + canvas = nil +end + +local glyph_calls = 0 +local unichar_calls = 0 + +local isTextVerbs = { + drawPosText = true, + drawPosTextH = true, + drawText = true, + drawTextOnPath = true, +} + +function sk_scrape_accumulate(t) + if isTextVerbs[t.verb] then + if t.glyphs then + glyph_calls = glyph_calls + 1 + else + unichar_calls = unichar_calls + 1 + end + end +end + +function sk_scrape_summarize() + io.write("glyph calls = ", glyph_calls, + ", unichar calls = ", unichar_calls, "\n"); +end + diff --git a/chromium/third_party/skia/tools/lua/classify_rrect_clips.lua b/chromium/third_party/skia/tools/lua/classify_rrect_clips.lua new file mode 100644 index 00000000000..792d169ab39 --- /dev/null +++ b/chromium/third_party/skia/tools/lua/classify_rrect_clips.lua @@ -0,0 +1,109 @@ + +function sk_scrape_startcanvas(c, fileName) end + +function sk_scrape_endcanvas(c, fileName) end + +function classify_rrect(rrect) + if (rrect:type() == "simple") then + local x, y = rrect:radii(0) + if (x == y) then + return "simple_circle" + else + return "simple_oval" + end + elseif (rrect:type() == "complex") then + local numNotSquare = 0 + local rx, ry + local same = true; + local first_not_square_corner + local last_not_square_corner + for i = 1, 4 do + local x, y = rrect:radii(i-1) + if (x ~= 0 and y ~= 0) then + if (numNotSquare == 0) then + rx = x + ry = y + first_not_square_corner = i + else + last_not_square_corner = i + if (rx ~= x or ry ~=y) then + same = false + end + end + numNotSquare = numNotSquare + 1 + end + end + local numSquare = 4 - numNotSquare + if (numSquare > 0 and same) then + local corners = "corners" + if (numSquare == 2) then + if ((last_not_square_corner - 1 == first_not_square_corner) or + (1 == first_not_square_corner and 4 == last_not_square_corner )) then + corners = "adjacent_" .. corners + else + corners = "opposite_" .. corners + end + elseif (1 == numSquare) then + corners = "corner" + end + if (rx == ry) then + return "circles_with_" .. numSquare .. "_square_" .. corners + else + return "ovals_with_" .. numSquare .. "_square_" .. corners + end + end + return "complex_unclassified" + elseif (rrect:type() == "rect") then + return "rect" + elseif (rrect:type() == "oval") then + local x, y = rrect:radii(0) + if (x == y) then + return "circle" + else + return "oval" + end + elseif (rrect:type() == "empty") then + return "empty" + else + return "unknown" + end +end + +function print_classes(class_table) + function sort_classes(a, b) + return a.count > b.count + end + array = {} + for k, v in pairs(class_table) do + if (type(v) == "number") then + array[#array + 1] = {class = k, count = v}; + end + end + table.sort(array, sort_classes) + local i + for i = 1, #array do + io.write(array[i].class, ": ", array[i].count, " (", array[i].count/class_table["total"] * 100, "%)\n"); + end +end + +function sk_scrape_accumulate(t) + if (t.verb == "clipRRect") then + local rrect = t.rrect + table["total"] = (table["total"] or 0) + 1 + local class = classify_rrect(rrect) + table[class] = (table[class] or 0) + 1 + end +end + +function sk_scrape_summarize() + print_classes(table) + --[[ To use the web scraper comment out the above call to print_classes, run the code below, + and in the aggregator pass agg_table to print_classes. + for k, v in pairs(table) do + if (type(v) == "number") then + local t = "agg_table[\"" .. k .. "\"]" + io.write(t, " = (", t, " or 0) + ", table[k], "\n" ); + end + end + --]] +end diff --git a/chromium/third_party/skia/tools/lua/count_effects.lua b/chromium/third_party/skia/tools/lua/count_effects.lua new file mode 100644 index 00000000000..f4469abbb90 --- /dev/null +++ b/chromium/third_party/skia/tools/lua/count_effects.lua @@ -0,0 +1,45 @@ +function tostr(t) + local str = "" + for k, v in next, t do + if #str > 0 then + str = str .. ", " + end + if type(k) == "number" then + str = str .. "[" .. k .. "] = " + else + str = str .. tostring(k) .. " = " + end + if type(v) == "table" then + str = str .. "{ " .. tostr(v) .. " }" + else + str = str .. tostring(v) + end + end + return str +end + +function sk_scrape_startcanvas(c, fileName) end + +function sk_scrape_endcanvas(c, fileName) end + +local effects = {} + +function count_fields(t) + for k, v in next, t do + effects[k] = (effects[k] or 0) + 1 + end +end + +local total_paints = 0; + +function sk_scrape_accumulate(t) + if (t.paint) then + total_paints = total_paints + 1 + count_fields(t.paint:getEffects()) + end +end + +function sk_scrape_summarize() + io.write("total paints ", total_paints, " ", tostr(effects), "\n"); +end + diff --git a/chromium/third_party/skia/tools/lua/count_reduced_clipstacks.lua b/chromium/third_party/skia/tools/lua/count_reduced_clipstacks.lua new file mode 100644 index 00000000000..1ea7a447001 --- /dev/null +++ b/chromium/third_party/skia/tools/lua/count_reduced_clipstacks.lua @@ -0,0 +1,87 @@ +stats = {} + +-- switch this to run on the automated scraper system +newline = "\n" +-- newline = "\\n" + +function sk_scrape_startcanvas(c, fileName) + canvas = c + oldstackstr = "<invalid>" +end + +function sk_scrape_endcanvas(c, fileName) + canvas = nil +end + +function string.starts(String,Start) + return string.sub(String,1,string.len(Start))==Start +end + +function build_stack_string(stack) + local info = "" + for i = 1, #stack do + local element = stack[i]; + info = info .. element["op"] .. ", " .. element["type"] .. ", aa:" .. tostring(element["aa"]) + if (element["type"] == "path") then + if (element["path"]:getSegmentTypes() == "line" and element["path"]:isConvex()) then + info = info .. ", convex_poly " .. element["path"]:countPoints() .. " points" + else + info = info .. ", fill: " .. element["path"]:getFillType() + info = info .. ", segments: (" .. element["path"]:getSegmentTypes() .. ")" + info = info .. ", convex:" .. tostring(element["path"]:isConvex()) + end + end + info = info .. newline + end + return info +end + +function sk_scrape_accumulate(t) + if (string.starts(t.verb, "draw")) then + local stack = canvas:getReducedClipStack() + local stackstr = build_stack_string(stack) + if (stackstr ~= "") then + if (stats[stackstr] == nil) then + stats[stackstr] = {} + stats[stackstr].drawCnt = 0 + stats[stackstr].instanceCnt = 0 + end + stats[stackstr].drawCnt = stats[stackstr].drawCnt + 1 + if (stackstr ~= oldstackstr) then + stats[stackstr].instanceCnt = stats[stackstr].instanceCnt + 1 + end + end + oldstackstr = stackstr + end +end + +function print_stats(stats) + function sort_by_draw_cnt(a, b) + return a.data.drawCnt > b.data.drawCnt + end + array = {} + for k,v in pairs(stats) do + array[#array + 1] = { name = k, data = v } + end + table.sort(array, sort_by_draw_cnt) + for i = 1, #array do + io.write("\n-------\n", array[i].name, tostring(array[i].data.drawCnt), " draws, ", tostring(array[i].data.instanceCnt), " instances.\n") + end +end + +function sk_scrape_summarize() + print_stats(stats) + --[[ To use the web scraper comment out the print above, run the code below to generate an + aggregate table on the automated scraper system. Then use the print_stats function on + agg_stats in the aggregator step. + for k,v in pairs(stats) do + if (v.drawCnt ~= nil) then + -- io.write("\n-------\n", k, tostring(v.drawCnt), " draws, ", tostring(v.instanceCnt), " instances.\n") + local tableEntry = 'agg_stats["' .. k .. '"]' + io.write(tableEntry, " = ", tableEntry, " or {}\n") + io.write(tableEntry, ".drawCnt = (", tableEntry, ".drawCnt or 0 ) + ", v.drawCnt, "\n") + io.write(tableEntry, ".instanceCnt = (", tableEntry, ".instanceCnt or 0 ) + ", v.instanceCnt, "\n") + end + end + --]] +end diff --git a/chromium/third_party/skia/tools/lua/dump_clipstack_at_restore.lua b/chromium/third_party/skia/tools/lua/dump_clipstack_at_restore.lua new file mode 100644 index 00000000000..eb5afb9bdba --- /dev/null +++ b/chromium/third_party/skia/tools/lua/dump_clipstack_at_restore.lua @@ -0,0 +1,33 @@ +function sk_scrape_startcanvas(c, fileName) + canvas = c + clipstack = {} + restoreCount = 0 +end + +function sk_scrape_endcanvas(c, fileName) + canvas = nil +end + +function sk_scrape_accumulate(t) + if (t.verb == "restore") then + restoreCount = restoreCount + 1; + -- io.write("Clip Stack at restore #", restoreCount, ":\n") + io.write("Reduced Clip Stack at restore #", restoreCount, ":\n") + for i = 1, #clipstack do + local element = clipstack[i]; + io.write("\t", element["op"], ", ", element["type"], ", aa:", tostring(element["aa"])) + if (element["type"] == "path") then + io.write(", fill: ", element["path"]:getFillType()) + io.write(", segments: \"", element["path"]:getSegmentTypes(), "\"") + io.write(", convex:", tostring(element["path"]:isConvex())) + end + io.write("\n") + end + io.write("\n") + else + -- clipstack = canvas:getClipStack() + clipstack = canvas:getReducedClipStack() + end +end + +function sk_scrape_summarize() end diff --git a/chromium/third_party/skia/tools/lua/dumpops.lua b/chromium/third_party/skia/tools/lua/dumpops.lua new file mode 100644 index 00000000000..1667e579fa9 --- /dev/null +++ b/chromium/third_party/skia/tools/lua/dumpops.lua @@ -0,0 +1,34 @@ +function tostr(t) + local str = "" + for k, v in next, t do + if #str > 0 then + str = str .. ", " + end + if type(k) == "number" then + str = str .. "[" .. k .. "] = " + else + str = str .. tostring(k) .. " = " + end + if type(v) == "table" then + str = str .. "{ " .. tostr(v) .. " }" + else + str = str .. tostring(v) + end + end + return str +end + +function sk_scrape_startcanvas(c, fileName) end + +function sk_scrape_endcanvas(c, fileName) end + +function sk_scrape_accumulate(t) + -- dump the params in t, specifically showing the verb first, which we + -- then nil out so it doesn't appear in tostr() + io.write(t.verb, " ") + t.verb = nil + io.write(tostr(t), "\n") +end + +function sk_scrape_summarize() end + diff --git a/chromium/third_party/skia/tools/lua/glyph-usage.lua b/chromium/third_party/skia/tools/lua/glyph-usage.lua new file mode 100644 index 00000000000..8715f726fe8 --- /dev/null +++ b/chromium/third_party/skia/tools/lua/glyph-usage.lua @@ -0,0 +1,157 @@ +function tostr(t) + local str = "" + for k, v in next, t do + if #str > 0 then + str = str .. ", " + end + if type(k) == "number" then + str = str .. "[" .. k .. "] = " + else + str = str .. tostring(k) .. " = " + end + if type(v) == "table" then + str = str .. "{ " .. tostr(v) .. " }" + else + str = str .. tostring(v) + end + end + return str +end + +local canvas -- holds the current canvas (from startcanvas()) + +--[[ + startcanvas() is called at the start of each picture file, passing the + canvas that we will be drawing into, and the name of the file. + + Following this call, there will be some number of calls to accumulate(t) + where t is a table of parameters that were passed to that draw-op. + + t.verb is a string holding the name of the draw-op (e.g. "drawRect") + + when a given picture is done, we call endcanvas(canvas, fileName) +]] +function sk_scrape_startcanvas(c, fileName) + canvas = c +end + +--[[ + Called when the current canvas is done drawing. +]] +function sk_scrape_endcanvas(c, fileName) + canvas = nil +end + +--[[ + Called with the parameters to each canvas.draw call, where canvas is the + current canvas as set by startcanvas() +]] + +function round(x, mul) + mul = mul or 1 + return math.floor(x * mul + 0.5) / mul +end + +dump_glyph_array_p = false + +function dump_array_as_C(array) + for k, v in next, array do + io.write(tostring(v), ", "); + end + io.write("-1,\n") +end + +local strikes = {} -- [fontID_pointsize] = [] unique glyphs + +function make_strike_key(paint) + return paint:getFontID() * 1000 + paint:getTextSize() +end + +-- array is an array of bools (true), using glyphID as the index +-- other is just an array[1...N] of numbers (glyphIDs) +function array_union(array, other) + for k, v in next, other do + array[v] = true; + end +end + +-- take a table of bools, indexed by values, and return a sorted table of values +function bools_to_values(t) + local array = {} + for k, v in next, t do + array[#array + 1] = k + end + table.sort(array) + return array +end + +function array_count(array) + local n = 0 + for k in next, array do + n = n + 1 + end + return n +end + +function sk_scrape_accumulate(t) + verb = t.verb; + if verb == "drawPosText" or verb == "drawPosTextH" then + if t.glyphs then + local key = make_strike_key(t.paint) + strikes[key] = strikes[key] or {} + array_union(strikes[key], t.glyphs) + + if dump_glyph_array_p then + dump_array_as_C(t.glyphs) + end + end + end +end + +--[[ + lua_pictures will call this function after all of the pictures have been + "accumulated". +]] +function sk_scrape_summarize() + local totalCount = 0 + local strikeCount = 0 + local min, max = 0, 0 + + local histogram = {} + + for k, v in next, strikes do + local fontID = round(k / 1000) + local size = k - fontID * 1000 + local count = array_count(v) + +-- io.write("fontID,", fontID, ", size,", size, ", entries,", count, "\n"); + + min = math.min(min, count) + max = math.max(max, count) + totalCount = totalCount + count + strikeCount = strikeCount + 1 + + histogram[count] = (histogram[count] or 0) + 1 + end + local ave = round(totalCount / strikeCount) + + io.write("\n", "unique glyphs: min = ", min, ", max = ", max, ", ave = ", ave, "\n"); + + for k, v in next, histogram do + io.write("glyph_count,", k, ",frequency,", v, "\n") + end +end + +function test_summary() + io.write("just testing test_summary\n") +end + +function summarize_unique_glyphIDs() + io.write("/* runs of unique glyph IDs, with a -1 sentinel between different runs */\n") + io.write("static const int gUniqueGlyphIDs[] = {\n"); + for k, v in next, strikes do + dump_array_as_C(bools_to_values(v)) + end + io.write("-1 };\n") +end + diff --git a/chromium/third_party/skia/tools/lua/gradients.lua b/chromium/third_party/skia/tools/lua/gradients.lua new file mode 100644 index 00000000000..b2d8cf77349 --- /dev/null +++ b/chromium/third_party/skia/tools/lua/gradients.lua @@ -0,0 +1,34 @@ + +function sk_scrape_startcanvas(c, fileName) end + +function sk_scrape_endcanvas(c, fileName) end + +count3 = 0 +count3sym = 0 + +function sk_scrape_accumulate(t) + local p = t.paint + if p then + local s = p:getShader() + if s then + local g = s:asAGradient() + if g then + --io.write(g.type, " gradient with ", g.colorCount, " colors\n") + + if g.colorCount == 3 then + count3 = count3 + 1 + + if (g.midPos >= 0.499 and g.midPos <= 0.501) then + count3sym = count3sym + 1 + end + end + end + end + end +end + +function sk_scrape_summarize() + io.write("Number of 3 color gradients: ", count3, "\n"); + io.write("Number of 3 color symmetric gradients: ", count3sym, "\n"); +end + diff --git a/chromium/third_party/skia/tools/lua/lua_app.cpp b/chromium/third_party/skia/tools/lua/lua_app.cpp new file mode 100644 index 00000000000..50b1352c2dc --- /dev/null +++ b/chromium/third_party/skia/tools/lua/lua_app.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkLua.h" +#include "SkGraphics.h" +#include "SkStream.h" +#include "SkData.h" +#include "SkOSFile.h" + +extern "C" { + #include "lua.h" + #include "lualib.h" + #include "lauxlib.h" +} + +static SkData* read_into_data(const char file[]) { + SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(file)); + if (!stream.get()) { + return SkData::NewEmpty(); + } + size_t len = stream->getLength(); + void* buffer = sk_malloc_throw(len); + stream->read(buffer, len); + return SkData::NewFromMalloc(buffer, len); +} + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + SkAutoGraphics ag; + SkLua L; + + for (int i = 1; i < argc; ++i) { + SkData* data = NULL; + const void* ptr; + size_t len; + + if (!strcmp(argv[i], "--lua") && i < argc-1) { + ptr = argv[i + 1]; + len = strlen(argv[i + 1]); + i += 1; + } else { + data = read_into_data(argv[i]); + ptr = data->data(); + len = data->size(); + } + if (!L.runCode(ptr, len)) { + SkDebugf("failed to load %s\n", argv[i]); + exit(-1); + } + SkSafeUnref(data); + } + return 0; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/lua/lua_pictures.cpp b/chromium/third_party/skia/tools/lua/lua_pictures.cpp new file mode 100644 index 00000000000..2985bf64c31 --- /dev/null +++ b/chromium/third_party/skia/tools/lua/lua_pictures.cpp @@ -0,0 +1,176 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "LazyDecodeBitmap.h" +#include "SkLua.h" +#include "SkLuaCanvas.h" +#include "SkPicture.h" +#include "SkCommandLineFlags.h" +#include "SkGraphics.h" +#include "SkStream.h" +#include "SkData.h" +#include "picture_utils.h" +#include "SkOSFile.h" +#include "SkImageDecoder.h" + +extern "C" { + #include "lua.h" + #include "lualib.h" + #include "lauxlib.h" +} + +static const char gStartCanvasFunc[] = "sk_scrape_startcanvas"; +static const char gEndCanvasFunc[] = "sk_scrape_endcanvas"; +static const char gAccumulateFunc[] = "sk_scrape_accumulate"; +static const char gSummarizeFunc[] = "sk_scrape_summarize"; + +// Example usage for the modulo flag: +// for i in {0..5}; do lua_pictures --skpPath SKP_PATH -l YOUR_SCRIPT --modulo $i 6 &; done +DEFINE_string(modulo, "", "[--modulo <remainder> <divisor>]: only run tests for which " + "testIndex %% divisor == remainder."); +DEFINE_string2(skpPath, r, "", "Read this .skp file or .skp files from this dir"); +DEFINE_string2(luaFile, l, "", "File containing lua script to run"); +DEFINE_string2(headCode, s, "", "Optional lua code to call at beginning"); +DEFINE_string2(tailFunc, s, "", "Optional lua function to call at end"); +DEFINE_bool2(quiet, q, false, "Silence all non-error related output"); + +static SkPicture* load_picture(const char path[]) { + SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path)); + SkPicture* pic = NULL; + if (stream.get()) { + pic = SkPicture::CreateFromStream(stream.get(), &sk_tools::LazyDecodeBitmap); + } + return pic; +} + +static SkData* read_into_data(const char file[]) { + SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(file)); + if (!stream.get()) { + return SkData::NewEmpty(); + } + size_t len = stream->getLength(); + void* buffer = sk_malloc_throw(len); + stream->read(buffer, len); + return SkData::NewFromMalloc(buffer, len); +} + +static void call_canvas(lua_State* L, SkLuaCanvas* canvas, + const char pictureFile[], const char funcName[]) { + lua_getglobal(L, funcName); + if (!lua_isfunction(L, -1)) { + int t = lua_type(L, -1); + SkDebugf("--- expected %s function %d, ignoring.\n", funcName, t); + lua_settop(L, -2); + } else { + canvas->pushThis(); + lua_pushstring(L, pictureFile); + if (lua_pcall(L, 2, 0, 0) != LUA_OK) { + SkDebugf("lua err: %s\n", lua_tostring(L, -1)); + } + } +} + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + SkCommandLineFlags::SetUsage("apply lua script to .skp files."); + SkCommandLineFlags::Parse(argc, argv); + + if (FLAGS_skpPath.isEmpty()) { + SkDebugf(".skp files or directories are required.\n"); + exit(-1); + } + if (FLAGS_luaFile.isEmpty()) { + SkDebugf("missing luaFile(s)\n"); + exit(-1); + } + + const char* summary = gSummarizeFunc; + if (!FLAGS_tailFunc.isEmpty()) { + summary = FLAGS_tailFunc[0]; + } + + SkAutoGraphics ag; + SkLua L(summary); + + for (int i = 0; i < FLAGS_luaFile.count(); ++i) { + SkAutoDataUnref data(read_into_data(FLAGS_luaFile[i])); + if (!FLAGS_quiet) { + SkDebugf("loading %s...\n", FLAGS_luaFile[i]); + } + if (!L.runCode(data->data(), data->size())) { + SkDebugf("failed to load luaFile %s\n", FLAGS_luaFile[i]); + exit(-1); + } + } + + if (!FLAGS_headCode.isEmpty()) { + L.runCode(FLAGS_headCode[0]); + } + + int moduloRemainder = -1; + int moduloDivisor = -1; + SkString moduloStr; + + if (FLAGS_modulo.count() == 2) { + moduloRemainder = atoi(FLAGS_modulo[0]); + moduloDivisor = atoi(FLAGS_modulo[1]); + if (moduloRemainder < 0 || moduloDivisor <= 0 || moduloRemainder >= moduloDivisor) { + SkDebugf("invalid modulo values.\n"); + return -1; + } + } + + for (int i = 0; i < FLAGS_skpPath.count(); i ++) { + SkTArray<SkString> paths; + if (sk_isdir(FLAGS_skpPath[i])) { + // Add all .skp in this directory. + const SkString directory(FLAGS_skpPath[i]); + SkString filename; + SkOSFile::Iter iter(FLAGS_skpPath[i], "skp"); + while(iter.next(&filename)) { + paths.push_back() = SkOSPath::SkPathJoin(directory.c_str(), filename.c_str()); + } + } else { + // Add this as an .skp itself. + paths.push_back() = FLAGS_skpPath[i]; + } + + for (int i = 0; i < paths.count(); i++) { + if (moduloRemainder >= 0) { + if ((i % moduloDivisor) != moduloRemainder) { + continue; + } + moduloStr.printf("[%d.%d] ", i, moduloDivisor); + } + const char* path = paths[i].c_str(); + if (!FLAGS_quiet) { + SkDebugf("scraping %s %s\n", path, moduloStr.c_str()); + } + + SkAutoTUnref<SkPicture> pic(load_picture(path)); + if (pic.get()) { + SkAutoTUnref<SkLuaCanvas> canvas( + new SkLuaCanvas(pic->width(), pic->height(), + L.get(), gAccumulateFunc)); + + call_canvas(L.get(), canvas.get(), path, gStartCanvasFunc); + canvas->drawPicture(pic); + call_canvas(L.get(), canvas.get(), path, gEndCanvasFunc); + + } else { + SkDebugf("failed to load\n"); + } + } + } + return 0; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/lua/scrape.lua b/chromium/third_party/skia/tools/lua/scrape.lua new file mode 100644 index 00000000000..627018800ac --- /dev/null +++ b/chromium/third_party/skia/tools/lua/scrape.lua @@ -0,0 +1,80 @@ +function tostr(t) + local str = "" + for k, v in next, t do + if #str > 0 then + str = str .. ", " + end + if type(k) == "number" then + str = str .. "[" .. k .. "] = " + else + str = str .. tostring(k) .. " = " + end + if type(v) == "table" then + str = str .. "{ " .. tostr(v) .. " }" + else + str = str .. tostring(v) + end + end + return str +end + +local total = {} -- accumulate() stores its data in here +local canvas -- holds the current canvas (from startcanvas()) + +--[[ + startcanvas() is called at the start of each picture file, passing the + canvas that we will be drawing into, and the name of the file. + + Following this call, there will be some number of calls to accumulate(t) + where t is a table of parameters that were passed to that draw-op. + + t.verb is a string holding the name of the draw-op (e.g. "drawRect") + + when a given picture is done, we call endcanvas(canvas, fileName) +]] +function sk_scrape_startcanvas(c, fileName) + canvas = c +end + +--[[ + Called when the current canvas is done drawing. +]] +function sk_scrape_endcanvas(c, fileName) + canvas = nil +end + +--[[ + Called with the parameters to each canvas.draw call, where canvas is the + current canvas as set by startcanvas() +]] +function sk_scrape_accumulate(t) + local n = total[t.verb] or 0 + total[t.verb] = n + 1 + + if false and t.verb == "drawRect" and t.paint:isAntiAlias() then + local r = t.rect; + local p = t.paint; + local c = p:getColor(); + print("drawRect ", tostr(r), tostr(c), "\n") + end + + if false and t.verb == "drawPath" then + local pred, r1, r2, d1, d2 = t.path:isNestedRects() + + if pred then + print("drawRect_Nested", tostr(r1), tostr(r2), d1, d2) + else + print("drawPath", "isEmpty", tostring(t.path:isEmpty()), + "isRect", tostring(t.path:isRect()), tostr(t.path:getBounds())) + end + end +end + +--[[ + lua_pictures will call this function after all of the pictures have been + "accumulated". +]] +function sk_scrape_summarize() + io.write("\n{ ", tostr(total), " }\n") +end + diff --git a/chromium/third_party/skia/tools/lua/scrape_dashing.lua b/chromium/third_party/skia/tools/lua/scrape_dashing.lua new file mode 100644 index 00000000000..48f353854ec --- /dev/null +++ b/chromium/third_party/skia/tools/lua/scrape_dashing.lua @@ -0,0 +1,93 @@ +function tostr(t) + local str = "" + for k, v in next, t do + if #str > 0 then + str = str .. ", " + end + if type(k) == "number" then + str = str .. "[" .. k .. "] = " + else + str = str .. tostring(k) .. " = " + end + if type(v) == "table" then + str = str .. "{ " .. tostr(v) .. " }" + else + str = str .. tostring(v) + end + end + return str +end + +local total_found = {} -- accumulate() stores its data in here +local total_total = {} +local canvas -- holds the current canvas (from startcanvas()) + +--[[ + startcanvas() is called at the start of each picture file, passing the + canvas that we will be drawing into, and the name of the file. + + Following this call, there will be some number of calls to accumulate(t) + where t is a table of parameters that were passed to that draw-op. + + t.verb is a string holding the name of the draw-op (e.g. "drawRect") + + when a given picture is done, we call endcanvas(canvas, fileName) +]] +function sk_scrape_startcanvas(c, fileName) + canvas = c +end + +--[[ + Called when the current canvas is done drawing. +]] +function sk_scrape_endcanvas(c, fileName) + canvas = nil +end + +function increment(table, key) + table[key] = (table[key] or 0) + 1 +end + + +local drawPointsTable = {} +local drawPointsTable_direction = {} + +function sk_scrape_accumulate(t) + increment(total_total, t.verb) + + local p = t.paint + if p then + local pe = p:getPathEffect(); + if pe then + increment(total_found, t.verb) + end + end + + if "drawPoints" == t.verb then + local points = t.points + increment(drawPointsTable, #points) + if 2 == #points then + if points[1].y == points[2].y then + increment(drawPointsTable_direction, "hori") + elseif points[1].x == points[2].x then + increment(drawPointsTable_direction, "vert") + else + increment(drawPointsTable_direction, "other") + end + end + end +end + +--[[ + lua_pictures will call this function after all of the pictures have been + "accumulated". +]] +function sk_scrape_summarize() + for k, v in next, total_found do + io.write(k, " = ", v, "/", total_total[k], "\n") + end + print("histogram of point-counts for all drawPoints calls") + print(tostr(drawPointsTable)) + print(tostr(drawPointsTable_direction)) +end + diff --git a/chromium/third_party/skia/tools/lua/scrape_dashing_full.lua b/chromium/third_party/skia/tools/lua/scrape_dashing_full.lua new file mode 100755 index 00000000000..5719e261274 --- /dev/null +++ b/chromium/third_party/skia/tools/lua/scrape_dashing_full.lua @@ -0,0 +1,146 @@ +local canvas -- holds the current canvas (from startcanvas()) + +--[[ + startcanvas() is called at the start of each picture file, passing the + canvas that we will be drawing into, and the name of the file. + + Following this call, there will be some number of calls to accumulate(t) + where t is a table of parameters that were passed to that draw-op. + + t.verb is a string holding the name of the draw-op (e.g. "drawRect") + + when a given picture is done, we call endcanvas(canvas, fileName) +]] +function sk_scrape_startcanvas(c, fileName) + canvas = c +end + +--[[ + Called when the current canvas is done drawing. +]] +function sk_scrape_endcanvas(c, fileName) + canvas = nil +end + +--[[ + Use to initialize all keys passed in keyTable to zero in table. + Useful so that keys that are never get incremented still output zero at end +]] +function resetTableKeys(table, keyTable) + for k, v in next, keyTable do + table[v] = 0 + end +end + +function increment(table, key) + table[key] = (table[key] or 0) + 1 +end + +local dashCount = 0 + +local total_found = {} +local drawPoints_count = {} +local drawPoints_direction = {} +resetTableKeys(drawPoints_direction, {"hori", "vert", "other"}) +local dashInterval_count = {} +local dashInterval_pattern = {} +resetTableKeys(dashInterval_pattern, {"one_one", "zero_on", "other"}) +local dash_phase = {} +resetTableKeys(dash_phase, {"zero", "other"}) +local dash_cap = {} +resetTableKeys(dash_cap, {"butt", "round", "square"}) + +local dashTable = {} +dashTable.total_found = total_found +dashTable.drawPoints_count = drawPoints_count +dashTable.drawPoints_direction = drawPoints_direction +dashTable.dashInterval_count = dashInterval_count +dashTable.dashInterval_pattern = dashInterval_pattern +dashTable.dash_phase = dash_phase +dashTable.dash_cap = dash_cap + +function sk_scrape_accumulate(t) + local p = t.paint + if p then + local pe = p:getPathEffect() + if pe then + local de = pe:asADash() + if de then + dashCount = dashCount + 1 + increment(total_found, t.verb); + increment(dashInterval_count, #de.intervals) + if 2 == #de.intervals then + if 1 == de.intervals[1] and 1 == de.intervals[2] then + increment(dashInterval_pattern, "one_one") + elseif 0 == de.intervals[1] then + increment(dashInterval_pattern, "zero_on") + else + increment(dashInterval_pattern, "other") + end + end + + if 0 == de.phase then + increment(dash_phase, "zero") + else + increment(dash_phase, "other") + end + + local cap = p:getStrokeCap() + if 0 == cap then + increment(dash_cap, "butt") + elseif 1 == cap then + increment(dash_cap, "round") + else + increment(dash_cap, "square") + end + + if "drawPoints" == t.verb then + local points = t.points + increment(drawPoints_count, #points) + if 2 == #points then + if points[1].y == points[2].y then + increment(drawPoints_direction, "hori") + elseif points[1].x == points[2].x then + increment(drawPoints_direction, "vert") + else + increment(drawPoints_direction, "other") + end + end + end + + --[[ + eventually would like to print out info on drawPath verbs with dashed effect + ]] + if "drawPath" == t.verb then + end + + end + end + end +end + +--[[ + lua_pictures will call this function after all of the pictures have been + "accumulated". +]] +function sk_scrape_summarize() +-- use for non telemetry +--[[ + io.write("Total dashed effects is: ", dashCount, "\n"); + for k1, v1 in next, dashTable do + io.write("\nTable: ", k1, "\n") + for k, v in next, v1 do + io.write("\"", k, "\": ", v, "\n") + end + end +]] + +-- use for telemetry + io.write("\ndashCount = dashCount + ", tostring(dashCount), "\n") + for k1, v1 in next, dashTable do + for k, v in next, v1 do + io.write("\nincrement(dashTable, \"", k1, "\", \"", k, "\", ", v, ")\n") + end + end +end + diff --git a/chromium/third_party/skia/tools/lua/skia.lua b/chromium/third_party/skia/tools/lua/skia.lua new file mode 100644 index 00000000000..e15e1227031 --- /dev/null +++ b/chromium/third_party/skia/tools/lua/skia.lua @@ -0,0 +1,91 @@ +-- Experimental helpers for skia -- + +function string.startsWith(String,Start) + return string.sub(String,1,string.len(Start))==Start +end + +function string.endsWith(String,End) + return End=='' or string.sub(String,-string.len(End))==End +end + + +Sk = {} + +function Sk.isFinite(x) + return x * 0 == 0 +end + +------------------------------------------------------------------------------- + +Sk.Rect = { left = 0, top = 0, right = 0, bottom = 0 } +Sk.Rect.__index = Sk.Rect + +function Sk.Rect.new(l, t, r, b) + local rect + if r then + -- 4 arguments + rect = { left = l, top = t, right = r, bottom = b } + elseif l then + -- 2 arguments + rect = { right = l, bottom = t } + else + -- 0 arguments + rect = {} + end + setmetatable(rect, Sk.Rect) + return rect; +end + +function Sk.Rect:width() + return self.right - self.left +end + +function Sk.Rect:height() + return self.bottom - self.top +end + +function Sk.Rect:isEmpty() + return self:width() <= 0 or self:height() <= 0 +end + +function Sk.Rect:isFinite() + local value = self.left * 0 + value = value * self.top + value = value * self.right + value = value * self.bottom + return 0 == value +end + +function Sk.Rect:setEmpty() + self.left = 0 + self.top = 0 + self.right = 0 + self.bottom = 0 +end + +function Sk.Rect:set(l, t, r, b) + self.left = l + self.top = t + self.right = r + self.bottom = b +end + +function Sk.Rect:offset(dx, dy) + dy = dy or dx + + self.left = self.left + dx + self.top = self.top + dy + self.right = self.right + dx + self.bottom = self.bottom + dy +end + +function Sk.Rect:inset(dx, dy) + dy = dy or dx + + self.left = self.left + dx + self.top = self.top + dy + self.right = self.right - dx + self.bottom = self.bottom - dy +end + +------------------------------------------------------------------------------- diff --git a/chromium/third_party/skia/tools/merge_static_libs.py b/chromium/third_party/skia/tools/merge_static_libs.py new file mode 100755 index 00000000000..842be18c84d --- /dev/null +++ b/chromium/third_party/skia/tools/merge_static_libs.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os +import shutil +import subprocess +import sys +import tempfile + +def _Usage(): + print 'Usage: merge_static_libs OUTPUT_LIB INPUT_LIB [INPUT_LIB]*' + sys.exit(1) + +def MergeLibs(in_libs, out_lib): + """ Merges multiple static libraries into one. + + in_libs: list of paths to static libraries to be merged + out_lib: path to the static library which will be created from in_libs + """ + if os.name == 'posix': + tempdir = tempfile.mkdtemp() + abs_in_libs = [] + for in_lib in in_libs: + abs_in_libs.append(os.path.abspath(in_lib)) + curdir = os.getcwd() + os.chdir(tempdir) + objects = [] + ar = os.environ.get('AR', 'ar') + for in_lib in abs_in_libs: + proc = subprocess.Popen([ar, '-t', in_lib], stdout=subprocess.PIPE) + proc.wait() + obj_str = proc.communicate()[0] + current_objects = obj_str.rstrip().split('\n') + proc = subprocess.Popen([ar, '-x', in_lib], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + proc.wait() + if proc.poll() == 0: + # The static library is non-thin, and we extracted objects + for object in current_objects: + objects.append(os.path.abspath(object)) + elif 'thin archive' in proc.communicate()[0]: + # The static library is thin, so it contains the paths to its objects + for object in current_objects: + objects.append(object) + else: + raise Exception('Failed to extract objects from %s.' % in_lib) + os.chdir(curdir) + if not subprocess.call([ar, '-crs', out_lib] + objects) == 0: + raise Exception('Failed to add object files to %s' % out_lib) + shutil.rmtree(tempdir) + elif os.name == 'nt': + subprocess.call(['lib', '/OUT:%s' % out_lib] + in_libs) + else: + raise Exception('Error: Your platform is not supported') + +def Main(): + if len(sys.argv) < 3: + _Usage() + out_lib = sys.argv[1] + in_libs = sys.argv[2:] + MergeLibs(in_libs, out_lib) + +if '__main__' == __name__: + sys.exit(Main()) diff --git a/chromium/third_party/skia/tools/misc_utils.py b/chromium/third_party/skia/tools/misc_utils.py new file mode 100644 index 00000000000..13978a47421 --- /dev/null +++ b/chromium/third_party/skia/tools/misc_utils.py @@ -0,0 +1,224 @@ +# Copyright 2014 Google Inc. +# +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +"""Module to host the VerboseSubprocess, ChangeDir, and ReSearch classes. +""" + +import os +import re +import subprocess + + +def print_subprocess_args(prefix, *args, **kwargs): + """Print out args in a human-readable manner.""" + def quote_and_escape(string): + """Quote and escape a string if necessary.""" + if ' ' in string or '\n' in string: + string = '"%s"' % string.replace('"', '\\"') + return string + if 'cwd' in kwargs: + print '%scd %s' % (prefix, kwargs['cwd']) + print prefix + ' '.join(quote_and_escape(arg) for arg in args[0]) + if 'cwd' in kwargs: + print '%scd -' % prefix + + +class VerboseSubprocess(object): + """Call subprocess methods, but print out command before executing. + + Attributes: + verbose: (boolean) should we print out the command or not. If + not, this is the same as calling the subprocess method + quiet: (boolean) suppress stdout on check_call and call. + prefix: (string) When verbose, what to print before each command. + """ + + def __init__(self, verbose): + self.verbose = verbose + self.quiet = not verbose + self.prefix = '~~$ ' + + def check_call(self, *args, **kwargs): + """Wrapper for subprocess.check_call(). + + Args: + *args: to be passed to subprocess.check_call() + **kwargs: to be passed to subprocess.check_call() + Returns: + Whatever subprocess.check_call() returns. + Raises: + OSError or subprocess.CalledProcessError: raised by check_call. + """ + if self.verbose: + print_subprocess_args(self.prefix, *args, **kwargs) + if self.quiet: + with open(os.devnull, 'w') as devnull: + return subprocess.check_call(*args, stdout=devnull, **kwargs) + else: + return subprocess.check_call(*args, **kwargs) + + def call(self, *args, **kwargs): + """Wrapper for subprocess.check(). + + Args: + *args: to be passed to subprocess.check_call() + **kwargs: to be passed to subprocess.check_call() + Returns: + Whatever subprocess.call() returns. + Raises: + OSError or subprocess.CalledProcessError: raised by call. + """ + if self.verbose: + print_subprocess_args(self.prefix, *args, **kwargs) + if self.quiet: + with open(os.devnull, 'w') as devnull: + return subprocess.call(*args, stdout=devnull, **kwargs) + else: + return subprocess.call(*args, **kwargs) + + def check_output(self, *args, **kwargs): + """Wrapper for subprocess.check_output(). + + Args: + *args: to be passed to subprocess.check_output() + **kwargs: to be passed to subprocess.check_output() + Returns: + Whatever subprocess.check_output() returns. + Raises: + OSError or subprocess.CalledProcessError: raised by check_output. + """ + if self.verbose: + print_subprocess_args(self.prefix, *args, **kwargs) + return subprocess.check_output(*args, **kwargs) + + def strip_output(self, *args, **kwargs): + """Wrap subprocess.check_output and str.strip(). + + Pass the given arguments into subprocess.check_output() and return + the results, after stripping any excess whitespace. + + Args: + *args: to be passed to subprocess.check_output() + **kwargs: to be passed to subprocess.check_output() + + Returns: + The output of the process as a string without leading or + trailing whitespace. + Raises: + OSError or subprocess.CalledProcessError: raised by check_output. + """ + if self.verbose: + print_subprocess_args(self.prefix, *args, **kwargs) + return str(subprocess.check_output(*args, **kwargs)).strip() + + def popen(self, *args, **kwargs): + """Wrapper for subprocess.Popen(). + + Args: + *args: to be passed to subprocess.Popen() + **kwargs: to be passed to subprocess.Popen() + Returns: + The output of subprocess.Popen() + Raises: + OSError or subprocess.CalledProcessError: raised by Popen. + """ + if self.verbose: + print_subprocess_args(self.prefix, *args, **kwargs) + return subprocess.Popen(*args, **kwargs) + + +class ChangeDir(object): + """Use with a with-statement to temporarily change directories.""" + # pylint: disable=I0011,R0903 + + def __init__(self, directory, verbose=False): + self._directory = directory + self._verbose = verbose + + def __enter__(self): + if self._directory != os.curdir: + if self._verbose: + print '~~$ cd %s' % self._directory + cwd = os.getcwd() + os.chdir(self._directory) + self._directory = cwd + + def __exit__(self, etype, value, traceback): + if self._directory != os.curdir: + if self._verbose: + print '~~$ cd %s' % self._directory + os.chdir(self._directory) + + +class ReSearch(object): + """A collection of static methods for regexing things.""" + + @staticmethod + def search_within_stream(input_stream, pattern, default=None): + """Search for regular expression in a file-like object. + + Opens a file for reading and searches line by line for a match to + the regex and returns the parenthesized group named return for the + first match. Does not search across newlines. + + For example: + pattern = '^root(:[^:]*){4}:(?P<return>[^:]*)' + with open('/etc/passwd', 'r') as stream: + return search_within_file(stream, pattern) + should return root's home directory (/root on my system). + + Args: + input_stream: file-like object to be read + pattern: (string) to be passed to re.compile + default: what to return if no match + + Returns: + A string or whatever default is + """ + pattern_object = re.compile(pattern) + for line in input_stream: + match = pattern_object.search(line) + if match: + return match.group('return') + return default + + @staticmethod + def search_within_string(input_string, pattern, default=None): + """Search for regular expression in a string. + + Args: + input_string: (string) to be searched + pattern: (string) to be passed to re.compile + default: what to return if no match + + Returns: + A string or whatever default is + """ + match = re.search(pattern, input_string) + return match.group('return') if match else default + + @staticmethod + def search_within_output(verbose, pattern, default, *args, **kwargs): + """Search for regular expression in a process output. + + Does not search across newlines. + + Args: + verbose: (boolean) shoule we call print_subprocess_args? + pattern: (string) to be passed to re.compile + default: what to return if no match + *args: to be passed to subprocess.Popen() + **kwargs: to be passed to subprocess.Popen() + + Returns: + A string or whatever default is + """ + if verbose: + print_subprocess_args('~~$ ', *args, **kwargs) + proc = subprocess.Popen(*args, stdout=subprocess.PIPE, **kwargs) + return ReSearch.search_within_stream(proc.stdout, pattern, default) + + diff --git a/chromium/third_party/skia/tools/pathops_sorter.htm b/chromium/third_party/skia/tools/pathops_sorter.htm new file mode 100644 index 00000000000..aac151af21e --- /dev/null +++ b/chromium/third_party/skia/tools/pathops_sorter.htm @@ -0,0 +1,2202 @@ +<!DOCTYPE html> + +<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta charset="utf-8" /> + <title></title> +<div style="height:0"> + +<div id="quad1"> +{{3.13,2.74}, {1.08,4.62}, {3.71,0.94}} +{{3.13,2.74}, {7.99,2.75}, {8.27,1.96}} +</div> + +<div id="quad2"> +{{4.838888984361574,4.399276078363981}, {5.947577332875065,2.02910379790342}, {3.8092258119951885,2.108659563498883}} +{{4.838888984361574,4.399276078363981}, {6.192910293864926,1.7797920604914939}, {3.3638348513490293,1.4969462106891218}} +</div> + +<div id="quad3"> +{{4.838888984361574,4.399276078363981}, {5.962263714769107,1.654601059605365}, {3.8789861259918847,2.8650082310420126}} +{{4.838888984361574,4.399276078363981}, {6.192910293864926,1.7797920604914939}, {3.3638348513490293,1.4969462106891218}} +</div> + +<div id="quad4"> +{{4.838888984361574,4.399276078363981}, {5.77868394109359,1.852867215174923}, {3.915702080726988,2.1820914729690903}} +{{4.838888984361574,4.399276078363981}, {6.681232491841801,2.5287975370876032}, {3.3638348513490293,1.4969462106891218}} +</div> + +<div id="quad5"> +{{4.838888984361574,4.399276078363981}, {6.082937568878361,1.9951156645288415}, {3.915702080726988,2.1820914729690903}} +{{4.838888984361574,4.399276078363981}, {6.681232491841801,2.5287975370876032}, {3.3638348513490293,1.4969462106891218}} +</div> + +<div id="quad6"> +{{4.898159171592373,4.367665311840888}, {6.695396170263287,1.769888953051804}, {3.6312051820191513,2.727377195492444}} +{{4.898159171592373,4.367665311840888}, {6.961778044734251,2.4813813873029633}, {3.3638348513490293,1.4969462106891218}} +</div> + +<div id="quad7"> +{{4.838888984361574,4.399276078363981}, {3.012741870322956,2.449520433298304}, {5.140619283496844,2.110967248292131}} +{{4.838888984361574,4.399276078363981}, {2.804962246947524,2.232446600933607}, {6.60393841996606,2.077794045550955}} +</div> + +<div id="quad8"> +{{4.838888984361574,4.399276078363981}, {3.1707957029384213,2.607574265913769}, {4.626944327496585,2.2848264641691425}} +{{4.838888984361574,4.399276078363981}, {2.804962246947524,2.232446600933607}, {6.60393841996606,2.077794045550955}} +</div> + +<div id="quad9"> +{{4.838888984361574,4.399276078363981}, {3.463749932092156,2.935940544745428}, {5.161344349908893,2.4940794849932386}} +{{4.838888984361574,4.399276078363981}, {2.804962246947524,2.232446600933607}, {6.60393841996606,2.077794045550955}} +</div> + +<div id="quad10"> +{{4.838888984361574,4.399276078363981}, {5.82508561259808,2.495362604119041}, {3.4377993053488463,2.7132154732530362}} +{{4.838888984361574,4.399276078363981}, {6.192910293864926,1.7797920604914939}, {2.435268584733173,1.817005221735438}} +</div> + +<div id="cubic1"> +{{0,0}, {1,0}, {0,1}, {1,1}} +{{0,0}, {2,0}, {0,2}, {2,2}} +</div> + +<div id="cubic2" > +{{0.4655213647959181,1.5608657525510201}, {0.6599868463010203,0.4290098852040817}, {2.473652742346939,1.2464524872448977}, {1.8511738679846936,0.5344786352040818}} +{{0.4655213647959181,1.5608657525510201}, {0.3250358737244896,0.819226323341837}, {1.4399214764030612,0.3318817761479596}, {1.2703414571528546,0.9081465322144181}} +</div> + +<div id="quad11"> +{{-378.22698974609375, -987.8935546875}, {-47.53326416015625, 482.7139892578125}, {-626.4708251953125, -338.62969970703125}} +{{-378.22698974609375, -987.8935546875}, {-847.94854736328125, -861.42230224609375}, {-390.9146728515625, 402.08740234375}} +</div> + +<div id="quad12"> +{{-173.3448486328125, -962.89422607421875}, {-778.321533203125, -161.47637939453125}, {-196.77374267578125, -736.40155029296875}} +{{-173.3448486328125, -962.89422607421875}, {652.3017578125, -400.67816162109375}, {-386.7855224609375, 361.1614990234375}} +</div> + +<div id="quad13"> +{{{-968.181396484375, 544.0128173828125}, {592.2825927734375, 870.552490234375}, {593.435302734375, 557.8828125}}} +{{{-968.181396484375, 544.0128173828125}, {593.677001953125, 865.5810546875}, {-66.57171630859375, -847.849853515625}}} +</div> + +<div id="quad14"> +{{{769.693115234375, -626.35089111328125}, {6.60491943359375, -210.43756103515625}, {-898.26654052734375, -17.76312255859375}}} +{{{769.693115234375, -626.35089111328125}, {192.8486328125, 609.8062744140625}, {888.317626953125, -551.27215576171875}}} +</div> + +<div id="quad15"> +{{{187.410400390625, -343.557373046875}, {-752.7930908203125, 431.57177734375}, {387.663330078125, 701.281982421875}}} +{{{187.410400390625, -343.557373046875}, {-86.16302490234375, -366.027099609375}, {-468.3883056640625, -25.736572265625}}} +</div> + +<div id="quad16"> +{{{-353.9388427734375, 76.8973388671875}, {-36.00189208984375, 282.289306640625}, {-531.37969970703125, 683.95751953125}}} +{{{-353.9388427734375, 76.8973388671875}, {-779.3529052734375, 509.6165771484375}, {-662.34088134765625, 124.4027099609375}}} +</div> + +<div id="quad17"> +{{{-657.0289306640625, 681.611083984375}, {-991.8365478515625, 964.4644775390625}, {-843.3585205078125, 904.47998046875}}} +{{{-657.0289306640625, 681.611083984375}, {-763.1571044921875, 39.1097412109375}, {618.2041015625, 840.6429443359375}}} +</div> + +<div id="quad18"> +{{{-609.406005859375, -684.37506103515625}, {766.4923095703125, 583.657958984375}, {-912.6832275390625, -949.553466796875}}} +{{{-609.406005859375, -684.37506103515625}, {774.140380859375, 82.2415771484375}, {540.9007568359375, -136.982666015625}}} +</div> + +<div id="quad19"> +{{{-657.0289306640625, 681.611083984375}, {-991.8365478515625, 964.4644775390625}, {-843.3585205078125, 904.47998046875}}} +{{{-657.0289306640625, 681.611083984375}, {-763.1571044921875, 39.1097412109375}, {618.2041015625, 840.6429443359375}}} +</div> + +<div id="quad20"> +{{{123.2955322265625, -577.799560546875}, {-491.892578125, 704.91748046875}, {478.03759765625, -951.92333984375}}} +{{{123.2955322265625, -577.799560546875}, {-550.6966552734375, 812.216796875}, {-816.3184814453125, -705.0025634765625}}} +</div> + +<div id="quad21"> +{{{123.2955322265625, -577.799560546875}, {-481.892578125, 704.91748046875}, {478.03759765625, -951.92333984375}}} +{{{123.2955322265625, -577.799560546875}, {-550.6966552734375, 812.216796875}, {-816.3184814453125, -705.0025634765625}}} +</div> + +<div id="quad22"> +{{{187.410400390625, -343.557373046875}, {-752.7930908203125, 431.57177734375}, {387.663330078125, 701.281982421875}}} +{{{187.410400390625, -343.557373046875}, {-86.16302490234375, -366.027099609375}, {-468.3883056640625, -25.736572265625}}} +</div> + +<div id="quad23"> +{{{-341.26922607421875, 964.1964111328125}, {883.2567138671875, 812.7301025390625}, {286.0372314453125, 94.979248046875}}} +{{{-341.26922607421875, 964.1964111328125}, {-158.90765380859375, 597.1875}, {-282.2255859375, 262.430908203125}}} +</div> + +<div id="quad24"> +{{{123.2955322265625, -577.799560546875}, {-481.892578125, 704.91748046875}, {478.03759765625, -951.92333984375}}} +{{{123.2955322265625, -577.799560546875}, {-550.6966552734375, 812.216796875}, {-816.3184814453125, -705.0025634765625}}} +{{{417.3499131065152, -577.799560546875}, {417.3499131065152, -699.60087482901156}, {331.22337542585541, -785.72740374616797}}} +</div> + +<div id="quad25"> +{{{922.6107177734375, 291.412109375}, {-939.361572265625, 589.8492431640625}, {-515.70941162109375, 120.2764892578125}}} +{{{922.6107177734375, 291.412109375}, {148.5115966796875, -751.42095947265625}, {-347.47503662109375, 331.1798095703125}}} +{{{922.6107177734375, -143.9114969433939}, {742.29377357777753, -143.9114969433939}, {614.79044900323777, -16.408159395199732}}} +{{{487.2871114550436, 291.412109375}, {487.2871114550436, 471.72905357065997}, {614.79044900323777, 599.23237814519973}}} +</div> + +<div id="quad26"> +{{{187.410400390625, -343.557373046875}, {-752.7930908203125, 431.57177734375}, {387.663330078125, 701.281982421875}}} +{{{187.410400390625, -343.557373046875}, {-86.16302490234375, -366.027099609375}, {-468.3883056640625, -25.736572265625}}} +{{{33.221887415632978, -343.557373046875}, {33.221887415632978, -279.69039894717827}, {78.38265915086852, -234.52963180711851}}} +</div> + +<div id="quad27"> +{{{-173.3448486328125, -962.89422607421875}, {-778.321533203125, -161.47637939453125}, {-196.77374267578125, -736.40155029296875}}} +{{{-173.3448486328125, -962.89422607421875}, {652.3017578125, -400.67816162109375}, {-386.7855224609375, 361.1614990234375}}} +{{{-270.84959533883426, -865.38947936819704}, {-230.46180860703427, -825.00168852687921}, {-173.3448486328125, -825.00168852687921}}} +{{{-75.840101926790737, -865.38947936819704}, {-35.4523110854729, -905.77726609999695}, {-35.4523110854729, -962.89422607421875}}} +</div> + +<div id="quad28"> +{{{344.2755126953125, -689.900390625}, {743.6728515625, 512.8448486328125}, {928.598388671875, 111.946044921875}}} +{{{344.2755126953125, -689.900390625}, {-950.03106689453125, -511.25741577148437}, {850.8173828125, 798.4874267578125}}} +{{{344.2755126953125, -689.900390625}, {850.8173828125, 798.4874267578125}}} +{{{344.2755126953125, -689.900390625}, {391.39917554828793, -551.43545842779145}}} +</div> + +<div id="quad29"> +{{{351.8946533203125, 512.8131103515625}, {-294.22332763671875, 183.2200927734375}, {624.4842529296875, 862.0753173828125}}} +{{{351.8946533203125, 512.8131103515625}, {489.1907958984375, -543.4212646484375}, {-432.7445068359375, 812.5205078125}}} +</div> + +<div id="quad30"> +{{{627.6910400390625, 81.144287109375}, {168.9248046875, -211.72735595703125}, {-61.57086181640625, 915.171875}}} +{{{627.6910400390625, 81.144287109375}, {918.159423828125, -325.468994140625}, {359.0523681640625, 817.4888916015625}}} +{{{235.78221371860315, 81.144287109375}, {235.78221371860315, 243.47824037936314}, {350.56965608373537, 358.26567106470213}}}, +</div> + +<div id="quad31"> +{{{178.1549072265625, 62.724609375}, {541.3643798828125, 223.823486328125}, {-446.77471923828125, -15.990478515625}}} +{{{178.1549072265625, 62.724609375}, {-347.14031982421875, -834.27191162109375}, {-495.13888549804687, 96.476806640625}}} +</div> + +<div id="quad32"> +{{{-809.41009521484375, 370.4566650390625}, {622.44677734375, -166.97119140625}, {-285.6748046875, 333.81005859375}}}, +{{{-809.41009521484375, 370.4566650390625}, {-110.36346435546875, -656.96044921875}, {906.4796142578125, 530.2061767578125}}} +</div> + +<div id="quad33"> +{{{-918.58624267578125, 653.6695556640625}, {-639.37548828125, 61.493896484375}, {-198.9605712890625, 243.704345703125}}}, +{{{-918.58624267578125, 653.6695556640625}, {-302.093505859375, -107.10955810546875}, {696.4962158203125, 600.738525390625}}} +</div> + +<div id="quad34"> +{{{-610.4193115234375, 861.173095703125}, {403.3203125, 215.3988037109375}, {-373.5546875, 179.88134765625}}}, +{{{-610.4193115234375, 861.173095703125}, {-757.244140625, -222.137451171875}, {705.892822265625, 87.4090576171875}}} +</div> + +<div id="quad35"> +{{{282.5767822265625, -529.4022216796875}, {392.0968017578125, 768.1014404296875}, {712.11572265625, 189.19677734375}}}, +{{{282.5767822265625, -529.4022216796875}, {699.360595703125, 465.6171875}, {438.5755615234375, 125.5230712890625}}} +</div> + +<div id="quad36"> +{{{-170.1510009765625, -184.905517578125}, {654.734130859375, 120.339599609375}, {-470.98443603515625, -69.4737548828125}}}, +{{{-170.1510009765625, -184.905517578125}, {-500.9822998046875, -148.40911865234375}, {-446.35821533203125, -840.5694580078125}}} +</div> + +<div id="quad37"> +{{{-119.55023193359375, -39.2008056640625}, {-618.14306640625, -620.1419677734375}, {-779.53790283203125, -681.9923095703125}}}, +{{{-119.55023193359375, -39.2008056640625}, {365.968994140625, 55.4974365234375}, {98.1297607421875, -192.474609375}}} +</div> + +<div id="quad38"> +{{{607.9136962890625, 484.1448974609375}, {280.619140625, 982.736572265625}, {-577.5596923828125, 798.9134521484375}}}, +{{{607.9136962890625, 484.1448974609375}, {374.318115234375, -590.5146484375}, {-258.30438232421875, 592.958984375}}} +</div> + +<div id="quad39"> +{{{-491.48846435546875, -470.9105224609375}, {109.7149658203125, -989.5384521484375}, {-275.900390625, 657.1920166015625}}}, +{{{-491.48846435546875, -470.9105224609375}, {-796.935791015625, 191.326171875}, {-852.120849609375, 62.06005859375}}} +</div> + +<div id="quad40"> +{{{-872.76458740234375, -163.30078125}, {723.6697998046875, 177.8204345703125}, {206.470703125, 147.9564208984375}}}, +{{{-872.76458740234375, -163.30078125}, {556.937744140625, 715.4345703125}, {627.348388671875, 77.0643310546875}}} +</div> + +<div id="quad108"> +{{{282.5767822265625, -529.4022216796875}, {392.0968017578125, 768.1014404296875}, {712.11572265625, 189.19677734375}}}, +{{{282.5767822265625, -529.4022216796875}, {699.360595703125, 465.6171875}, {438.5755615234375, 125.5230712890625}}} +</div> + +<div id="quad159"> +{{{-868.3076171875, -212.74591064453125}, {-208.84014892578125, -57.353515625}, {393.79736328125, -986.03607177734375}}}, +{{{-868.3076171875, -212.74591064453125}, {371.0980224609375, -960.9017333984375}, {-236.2821044921875, -441.20074462890625}}} +</div> + +<div id="quad212"> +{{{-610.4193115234375, 861.173095703125}, {403.3203125, 215.3988037109375}, {-373.5546875, 179.88134765625}}}, +{{{-610.4193115234375, 861.173095703125}, {-757.244140625, -222.137451171875}, {705.892822265625, 87.4090576171875}}} +</div> + +<div id="quad232"> +{{{766.497802734375, 675.660400390625}, {639.0235595703125, 351.4776611328125}, {345.9315185546875, 624.685791015625}}}, +{{{766.497802734375, 675.660400390625}, {-901.72650146484375, 923.99169921875}, {755.665283203125, 416.728759765625}}} +</div> + +<div id="quad379"> +{{{-872.76458740234375, -163.30078125}, {723.6697998046875, 177.8204345703125}, {206.470703125, 147.9564208984375}}}, +{{{-872.76458740234375, -163.30078125}, {556.937744140625, 715.4345703125}, {627.348388671875, 77.0643310546875}}} +</div> + +<div id="quad413"> +{{{-127.60784912109375, 384.614990234375}, {-184.46685791015625, 717.5728759765625}, {-981.56524658203125, -827.18109130859375}}}, +{{{-127.60784912109375, 384.614990234375}, {-125.78131103515625, 751.187744140625}, {562.529541015625, -277.5535888671875}}} +</div> + +<div id="quad179"> +{{{-595.956298828125, -113.24383544921875}, {-730.611572265625, 481.5323486328125}, {505.58447265625, -504.9130859375}}}, +{{{-595.956298828125, -113.24383544921875}, {-971.0836181640625, -849.73907470703125}, {-32.39227294921875, -906.3277587890625}}} +</div> + +<div id="quad584"> +{{{-406.65435791015625, 599.96630859375}, {-566.71881103515625, -400.65362548828125}, {-486.0682373046875, 100.34326171875}}}, +{{{-406.65435791015625, 599.96630859375}, {799.783935546875, 992.77783203125}, {180.6688232421875, -490.0054931640625}}} +</div> + +<div id="quad653"> +{{{-46.6143798828125, 164.224853515625}, {-161.7724609375, 327.61376953125}, {168.5106201171875, -948.4150390625}}}, +{{{-46.6143798828125, 164.224853515625}, {412.9364013671875, -199.26715087890625}, {-278.044677734375, 472.3961181640625}}} +</div> + +<div id="quad809"> +{{{-176.8541259765625, -275.9761962890625}, {-723.969482421875, -7.4718017578125}, {931.6959228515625, 231.6737060546875}}}, +{{{-176.8541259765625, -275.9761962890625}, {-250.86737060546875, -748.8143310546875}, {-96.77099609375, -287.76336669921875}}} +</div> + +<div id="quad14a"> +{{{-609.406005859375, -684.37506103515625}, {766.4923095703125, 583.657958984375}, {-912.6832275390625, -949.553466796875}}}, +{{{-609.406005859375, -684.37506103515625}, {774.140380859375, 82.2415771484375}, {540.9007568359375, -136.982666015625}}} +</div> + +<div id="quad22a"> +{{{-728.5626220703125, 141.134521484375}, {749.9122314453125, -645.93359375}, {67.1751708984375, -285.85528564453125}}}, +{{{-728.5626220703125, 141.134521484375}, {-841.0341796875, -988.058349609375}, {34.87939453125, -489.359130859375}}} +{{{276.48354206343231, -395.24293552482953}, {-728.5626220703125, 141.134521484375}}} +{{{fX=97.702285839737073, -301.95147049201717}, {-728.5626220703125, 141.134521484375}}} +{{{fX=-52.525628917174856, -536.31069276053427}, {-728.5626220703125, 141.134521484375}}} +{{{fX=-5.2463328209585285, -511.63085965304060}, {-728.5626220703125, 141.134521484375}}} +</div> + +<div id="quad77"> +{{{383.7933349609375, -397.5057373046875}, {480.7408447265625, 92.927490234375}, {690.7930908203125, -267.44964599609375}}}, +{{{383.7933349609375, -397.5057373046875}, {83.3685302734375, 619.781005859375}, {688.14111328125, 416.241455078125}}} +</div> + +<div id="quad94"> +{{{627.6910400390625, 81.144287109375}, {168.9248046875, -211.72735595703125}, {-61.57086181640625, 915.171875}}}, +{{{627.6910400390625, 81.144287109375}, {918.159423828125, -325.468994140625}, {359.0523681640625, 817.4888916015625}}} +{{{564.43435948662466, 47.034527772832369}, {627.6910400390625, 81.144287109375}}} +{{{699.34014109378302, 79.147174806567705}, {627.6910400390625, 81.144287109375}}} +</div> + +<div id="quad4a"> +{{{187.410400390625, -343.557373046875}, {-752.7930908203125, 431.57177734375}, {387.663330078125, 701.281982421875}}}, +{{{187.410400390625, -343.557373046875}, {-86.16302490234375, -366.027099609375}, {-468.3883056640625, -25.736572265625}}} +</div> + +<div id="quad0"> +{{{-708.0077926931004413, -154.6166947224404566}, {-701.0429781735874712, -128.8517387364408933}, {505.58447265625, -504.9130859375}}}, +{{{-708.0077926931004413, -154.6166947224404566}, {-721.5125661899801344, -174.4028951148648048}, {-32.39227294921875, -906.3277587890625}}} +{{{-707.8363172079705237, -154.25350453766481}, {-708.0077926931004413, -154.6166947224404566}}} +{{{-708.1792267111628689, -154.9799046892118213}, {-708.0077926931004413, -154.6166947224404566}}} +</div> + +<div id="quad999"> +{{{-708.00779269310044, -154.61669472244046}, {-707.92342686353186, -154.30459999551294}, {505.58447265625, -504.9130859375}}}, +{{{-708.00779269310044, -154.61669472244046}, {-708.1713780141481, -154.85636789757655}, {-32.39227294921875, -906.3277587890625}}} +{{{-708.0077672218041, -154.61664072892336}, {-708.00779269310044, -154.61669472244046}}} +{{{-708.00781827681976, -154.61674895426012}, {-708.00779269310044, -154.61669472244046}}} +</div> + +<div id="quad113"> +{{{425.018310546875, -866.61865234375}, {-918.76531982421875, 209.05322265625}, {964.34716796875, 199.52587890625}}}, +{{{425.018310546875, -866.61865234375}, {703.10693359375, -955.0738525390625}, {-952.24664306640625, -717.94775390625}}} +</div> + +<div id="quad136"> +{{{178.1549072265625, 62.724609375}, {541.3643798828125, 223.823486328125}, {-446.77471923828125, -15.990478515625}}}, +{{{178.1549072265625, 62.724609375}, {-347.14031982421875, -834.27191162109375}, {-495.138885498046875, 96.476806640625}}} +</div> + +<div id="quad206"> +{{{-503.007415771484375, -318.59490966796875}, {-798.330810546875, -881.21630859375}, {-127.2027587890625, 769.6160888671875}}}, +{{{-503.007415771484375, -318.59490966796875}, {-153.6217041015625, -776.896728515625}, {-378.43701171875, -296.3197021484375}}} +{{{-468.9176053311167607, -89.39573455985038208}, {-503.007415771484375, -318.59490966796875}}} +{{{-356.1573846604815685, -497.6768266540607328}, {-503.007415771484375, -318.59490966796875}}} +{{{-559.0376987487186398, -420.2054253473417589}, {-503.007415771484375, -318.59490966796875}}} +{{{-431.6586315464865606, -409.8353728177644371}, {-503.007415771484375, -318.59490966796875}}} +</div> + +<div id="quad640"> +{{{412.260498046875, 49.193603515625}, {838.97900390625, 86.9951171875}, {427.7896728515625, -605.6881103515625}}}, +{{{412.260498046875, 49.193603515625}, {-995.54583740234375, 990.032470703125}, {-881.18670654296875, 461.211669921875}}} +</div> + +<div id="quad3160"> +{{{426.645751953125, 813.79150390625}, {-387.23828125, -588.89483642578125}, {792.4261474609375, -704.4637451171875}}}, +{{{426.645751953125, 813.79150390625}, {19.24896240234375, -416.09906005859375}, {233.8497314453125, 350.778564453125}}} +</div> + +<div id="quad35237"> +{{{-770.8492431640625, 948.2369384765625}, {-853.37066650390625, 972.0301513671875}, {-200.62042236328125, -26.7174072265625}}}, +{{{-770.8492431640625, 948.2369384765625}, {513.602783203125, 578.8681640625}, {960.641357421875, -813.69757080078125}}} +</div> + +<div id="quad37226"> +{{{563.8267822265625, -107.4566650390625}, {-44.67724609375, -136.57452392578125}, {492.3856201171875, -268.79644775390625}}}, +{{{563.8267822265625, -107.4566650390625}, {708.049072265625, -100.77789306640625}, {-48.88226318359375, 967.9022216796875}}} +</div> + +<div id="quad67242"> +{{{598.857421875, 846.345458984375}, {-644.095703125, -316.12921142578125}, {-97.64599609375, 20.6158447265625}}}, +{{{598.857421875, 846.345458984375}, {715.7142333984375, 955.3599853515625}, {-919.9478759765625, 691.611328125}}} +</div> + +<div id="quad208"> +{{{481.1463623046875, -687.09613037109375}, {643.64697265625, -951.9462890625}, {162.5869140625, 698.7342529296875}}}, +{{{481.1463623046875, -687.09613037109375}, {171.8175048828125, -919.07977294921875}, {153.3433837890625, -587.43072509765625}}} +</div> + +<div id="quad8a"> +{{{344.2755126953125, -689.900390625}, {743.6728515625, 512.8448486328125}, {928.598388671875, 111.946044921875}}}, +{{{344.2755126953125, -689.900390625}, {-950.03106689453125, -511.25741577148437}, {850.8173828125, 798.4874267578125}}} +</div> + +<div id="quad8b"> +{{{344.2755126953125, -689.900390625}, {928.598388671875, 111.946044921875}, {743.6728515625, 512.8448486328125}}}, +{{{344.2755126953125, -689.900390625}, {-950.03106689453125, -511.25741577148437}, {850.8173828125, 798.4874267578125}}} +</div> + +<div id="quad8741"> +{{{944.9024658203125, 939.454345703125}, {-971.06219482421875, -914.24395751953125}, {-878.764404296875, -297.61602783203125}}}, +{{{944.9024658203125, 939.454345703125}, {-838.96612548828125, -785.837646484375}, {-126.80029296875, 921.1981201171875}}} +{{{107.03238931174118, 218.460612766889}, {944.9024658203125, 939.454345703125}}} +{{{-292.72752350740279, 99.917575976335598}, {944.9024658203125, 939.454345703125}}} +</div> + +<div id="quad89987"> +{{{939.4808349609375, 914.355224609375}, {-357.7921142578125, 590.842529296875}, {736.8936767578125, -350.717529296875}}}, +{{{939.4808349609375, 914.355224609375}, {-182.85418701171875, 634.4552001953125}, {-509.62615966796875, 576.1182861328125}}} +</div> + +<div id="simplifyQuadratic36"> +{{{1.9474306106567383, 2.3777823448181152}, {1.9234547048814592, 2.2418855043499213}, {1.8885438442230225, 2.1114561557769775}}} +{{{1.9474306106567383, 2.3777823448181152}, {2.0764266380046235, 2.2048800651418379}, {1.8888888359069824, 2.1111111640930176}}} +</div> + +<div id="simplifyQuadratic58"> +{{326.236786,205.854996}, {329.104431,231.663818}, {351.512085,231.663818}} +{{303.12088,141.299606}, {330.463562,217.659027}} +</div> + +<div id="simplifyQuadratic58a"> +{{{326.23678588867188, 205.85499572753906}, {328.04376176056422, 222.11778818951981}, {337.6092529296875, 228.13298034667969} +{{{303.12088012695312, 141.29960632324219}, {330.46356201171875, 217.65902709960937} +</div> + +<div id="quadratic58again"> +{{322.935669,231.030273}, {312.832214,220.393295}, {312.832214,203.454178}} +{{322.12738,233.397751}, {295.718353,159.505829}} +</div> + +<div id="simplifyQuadratic56"> +{{{380.29449462890625, 140.44486999511719}, {387.29080200195312, 136.67460632324219}, {396.0399169921875, 136.67460632324219}}} +{{{380.29449462890625, 140.44486999511719}, {388.29925537109375, 136.67460632324219}, {398.16494750976562, 136.67460632324219}}} +{{{380.29449462890625, 140.44486999511719}, {387.692810, 137.858429}}} +</div> + +<div id="simplifyQuadratic56a"> +{{{380.29449462890625, 140.44486999511719}, {387.29079954793264, 136.67460632324219}, {396.0399169921875, 136.67460632324219}}} +{{{380.29449462890625, 140.44486999511719}, {388.29925767018653, 136.67460632324219}, {398.16494750976562, 136.67460632324219}}} +{{fX=380.29449462890625 fY=140.44486999511719 }, {fX=398.16494750976562 fY=136.67460632324219 }} } +{{fX=380.29449462890625 fY=140.44486999511719 }, {fX=396.03991699218750 fY=136.67460632324219 }} +</div> + +<div id="simplifyQuadratic27"> +{{{1, 1}, {1, 0.666666687f}, {0.888888896f, 0.444444448f}}} +{{{1, 1}, {1, 0.5f}, {0, 0}}} +{{fX=1.0000000000000000 fY=1.0000000000000000 }, {fX=0.00000000000000000 fY=0.00000000000000000 }} } +{{fX=1.0000000000000000 fY=1.0000000000000000 }, {fX=0.88888889551162720 fY=0.44444444775581360 }} } +</div> + +<div id="cubicOp7d"> +{{{0.7114982008934021, 1.6617077589035034}, {0.51239079236984253, 1.4952657222747803}, {0.27760171890258789, 1.2776017189025879}, {0, 1}}} +{{{0.7114982008934021, 1.6617077589035034}, {0.20600014925003052, 1.7854888439178467}, {9.8686491813063348e-017, 1.9077447652816772}, {0, 1}}} +</div> + +<div id="cubicOp25i"> +{{{3.3856770992279053, 1.6298094987869263}, {3.777235186270762, 1.2744716237277114}, {3.7191683314895783, 1.4127666421509713}, {3.3995792865753174, 1.6371387243270874}}} +{{{3.3856770992279053, 1.6298094987869263}, {3.3902986605112582, 1.6322361865810757}, {3.3949326825525121, 1.6346792563210237}, {3.3995792865753174, 1.6371387243270874}}} +{{3.3856770992279053, 1.6298094987869263 }, {3.3995792865753174, 1.6371387243270874 }} +</div> + +<div id="eldorado1"> +{{{1006.69513f, 291}, {1023.26367f, 291}, {1033.84021f, 304.431458f}, {1030.31836f, 321}}} +{{{1030.318359375, 321}, {1036.695068359375, 291}}} +{{fX=1030.3183593750000 fY=321.00000000000000 }, {fX=1006.6951293945312 fY=291.00000000000000 }} } +</div> + +<div id="carpetplanet1"> +{{fX=67.000000000000000, 913.00000000000000 }, {194.00000000000000, 1041.0000000000000 }} } +{{fX=67.000000000000000, 913.00000000000000 }, {67.662002563476562, 926.00000000000000 }} } +{{{67, 913}, {67, 917.388977f}, {67.223999f, 921.726013f}, {67.6620026f, 926}}} +{{{67, 913}, {67, 983.692017f}, {123.860001f, 1041}, {194, 1041}}} +{{{67, 913}, {67.17070902440698, 919.69443917507760}}} +</div> + +<div id="cubicOp104"> +{{{2.25, 2.5}, {4.5, 1}}} +{{{2.25, 2.5}, {3.0833333333333321, 1.9999999999999973}, {4.0277778307596899, 1.2777777777777759}, {4.8611111640930176, 1}}} +{{{2.25, 2.5}, {1.9476099234472042, 2.6814340459316774}, {1.6598502000264239, 2.8336073904096661}, {1.3973386287689209, 2.9246666431427002}}} +{{{2.25, 2.5}, {1.2674896717071533, 3.1550068855285645}}} +</div> + +<div id="cubicOp105"> +{{{2.4060275554656982, 3.4971563816070557}, {2.9702522134213849, 4.2195279679982622}, {3.8172613958721247, 5.0538091166976979}, {5, 6}}} +{{{2.4060275554656982, 3.4971563816070557}, {3.4194286958002023, 3.5574883660881684}, {4.0077197935900575, 2.6628073781813661}, {2.2602717876434326, 0.33545622229576111}}} +</div> + +<div id="cubicOp106"> +{{{0.80825299024581909, 1.9691258668899536}, {0.8601454496383667, 1.9885541200637817}, {0.92434978485107422, 2}, {1, 2}}} +{{{0.80825299024581909, 1.9691258668899536}, {2.2400102615356445, 3.5966837406158447}, {2.5486805438995361, 3.362929105758667}, {2.494147777557373, 2.5976591110229492}}} +{{{0.80825299024581909, 1.9691258668899536}, {2.494147777557373, 2.5976591110229492}}} +{{{0.80825299024581909, 1.9691258668899536}, {1, 2}}} +</div> + +<div id="cubicOp109"> +{{{5, 4}, {5.443139240552143931, 3.556860759447856069}, {5.297161243696338673, 3.702838775882067335}, {4.649086475372314453, 3.654751062393188477}}} +{{{5, 4}, {4.876459521889748849, 3.876459521889748849}, {4.759596556247283949, 3.761504502886134915}, {4.649086475372314453, 3.654751062393188477}}} +</div> + +<div id="skpwww_joomla_org_23"> +{{{421, 378}, {421, 380.209137f}, {418.761414f, 382}, {416, 382}}} +{{{320, 378}, {421, 378.000031f}}} +{{{421, 378.000031f}, {421, 383}}} +{{{416, 383}, {418.761414f, 383}, {421, 380.761414f}, {421, 378}}} +</div> + +<div id="xop1i"> +{{5.000,1.000}, {5.191,0.809}, {5.163,0.837}, {4.993,1.000}} +{{5.000,1.000}, {4.968,1.024}} +{{5.000,1.000}, {4.998,1.000}, {4.995,1.000}, {4.993,1.000}} +</div> + +<div id="xop1u"> +{{3.500,3.500}, {3.000,4.000}, {2.500,4.500}, {1.000,4.000}} +{{3.500,3.500}, {3.113,3.887}, {2.725,4.275}, {2.338,3.732}} +</div> + +<div id="xOp2i"> +{{{2, 3}, {1.3475509011665685, 4.9573472965002949}, {2.8235509286078759, 3.5091759365574173}, {3.6505906581878662, 1.9883773326873779}}} +{{{2, 3}, {2.4604574005585795, 2.654656949581065}, {3.0269255632437986, 2.3093137214344743}, {3.6505906581878662, 1.9883773326873779}}} +{{{2, 3}, {1.0000000000000013, 3.7500000000000004}, {0.500000000000001, 4.5}, {1, 5}}} +</div> + +<div id="testQuadratic56"> +{{{380.29449462890625, 140.44486999511719}, {379.59701460635523, 140.8207374882179}, {378.91729736328125, 141.23385620117187}}} +{{{380.29449462890625, 140.44486999511719}, {387.29079954793264, 136.67460632324219}, {396.0399169921875, 136.67460632324219}}} +{{{380.29449462890625, 140.44486999511719}, {388.29925767018653, 136.67460632324219}, {398.16494750976562, 136.67460632324219}}} +</div> + +<div id="testQuad15"> +{{{1, 3}, {1, 1}}} +{{{1, 3}, {0, 0}}} +{{{1, 3}, {2, 0}, {0, 0}}} +</div> + +<div id="testQuad21"> +{{{0, 0}, {1, 1}}} +{{{0, 0}, {3, 0}, {2, 3}}} +{{{0, 0}, {2, 3}}} +{{{0, 0}, {2, 1}}} +</div> + +<div id="testQuad22"> +{{{0, 0}, {1.2000000476837158, 0.80000001192092896}}} +{{{0, 0}, {2, 0}}} +{{{0, 0}, {0, 1}, {3, 2}}} +{{{0, 0}, {1, 1}}} +</div> + +<div id="testQuad23"> +{{{1, 3}, {1.9090908914804459, 1.1818182170391081}, {0.33884298801422119, 1.0165289640426636}}} +{{{1, 3}, {0.33884298801422119, 1.0165289640426636}}} +{{{1, 3}, {3, 0}}} +</div> + +<div id="cubicOp35d"> +{{{2.211416482925415, 1.6971458196640015}, {1.2938009262874868, 2.8273619288830005}, {0.64690048634813535, 3.5876019453925414}, {0, 1}}} +{{{2.211416482925415, 1.6971458196640015}, {1.0082962512969971, 1.997925877571106}}} +{{{2.211416482925415, 1.6971458196640015}, {5, 1}}} +</div> + +<div id="skpnational_com_au81"> +{{{1110.7071533203125, 817.29290771484375}, {1110.9998779296875, 817.58587646484375}, {1111, 818}}} +{{{1110.7071533203125, 817.29290771484375}, {1110.526180767307778, 817.1119214508684081}, {1110.276144384999725, 817}, {1110, 817}}} +{{{1110.7071533203125, 817.29290771484375}, {1110.888097894721341, 817.4738660071997174}, {1111, 817.7238677851287321}, {1111, 818}}} +{{{1110.7071533203125, 817.29290771484375}, {1110.4140625, 817.0001220703125}, {1110, 817}}} +</div> + +<div id="cubicOp85d"> +{{{1.0648664236068726, 2.9606373310089111}, {0.80208036362614099, 2.7936484180272374}, {0.49170560730211144, 2.2292640182552783}, {0, 1}}} +{{{1.0648664236068726, 2.9606373310089111}, {0.6261905430171294, 3.2248910899179175}, {0.38860045191888659, 2.9430022595944321}, {0, 1}}} +{{{1.0648664236068726, 2.9606373310089111}, {1.4282355765013004, 3.191542348791669}, {1.7006143409852195, 2.6626209548338378}, {2.2355968952178955, 2.0810616016387939}}} +{{{1.0648664236068726, 2.9606373310089111}, {1.3437142856601656, 2.7926622975690494}, {1.7038131304059798, 2.4040122748806132}, {2.2355968952178955, 2.0810616016387939}}} +</div> + +<div id="testQuads22"> +{{{0, 0}, {1.20000004768371582, 0.8000000119209289551}}} +{{{0, 0}, {2, 0}}} +{{{0, 0}, {0, 1}, {3, 2}}} +{{{0, 0}, {1, 1}}} +</div> + +<div id="cubicOp59d"> +{{{4, 1}, {4, 0.37698365082686142}, {4.3881493046286568, 2.4710128800004547}, {3.4716842174530029, 2.9292664527893066}}} +{{{4, 1}, {0, 1}}} +</div> + +<div id="findFirst1"> +{{{2.5767931938171387, 0.88524383306503296}, {2.4235948002142855, 0.88692501490384834}, {2.2328897699358898, 0.92237007668803672}, {2, 1}}} +{{{2.5767931938171387, 0.88524383306503296}, {1.6008643534817426, 1.1609015907463158}, {1.1200849961943122, 1.8138386966264941}, {0.75343161821365356, 2.7170474529266357}}} +{{{2.5767931938171387, 0.88524383306503296}, {4.0492746201577932, 0.86908498848619054}, {2.0567957107800288, 3.9721309710522448}, {0.75343161821365356, 2.7170474529266357}}} +{{{2.5767931938171387, 0.88524383306503296}, {3.3470152174198557, 0.66768936887879282}, {4.4256496583071421, 0.68512993166142844}, {6, 1}}} +{{{2.57679319, 0.885243833}, {5.15358639, 0.885243833}}} +</div> + +<div id="testQuads54"> +{{1.000,1.000}, {1.500,0.500}, {1.500,0.250}} +{{1.000,1.000}, {1.667,0.333}} +{{1.000,1.000}, {2.000,3.000}} +</div> + +<div id="testQuads45"> +{{{3, 3}, {3, 2.7999999523162842}, {2.880000114440918, 2.6400001049041748}}} +{{{3, 3}, {3, 2}, {2, 0}}} +{{{3, 3}, {2, 0}}} +{{{3, 3}, {2.880000114440918, 2.6400001049041748}}} +</div> + +<div id="testQuads59"> +{{{3, 1}, {3, 0}}} +{{{3, 1}, {2.6666667461395264, 0.66666668653488159}}} +{{{3, 1}, {2.8000003099441542, 1.1999996900558463}, {2.6800000667572021, 1.3600000143051147}}} +{{{3, 1}, {2.6666667461395264, 1.3333333730697632}}} +</div> + +<div id="skpcarrot_is24"> +{{{1020.08099, 672.161987}, {1020.08051, 651.450988}, {1011.68576, 632.700988}, {998.113511, 619.128738}}} +{{{1020.08099, 672.161987}, {1019.27607, 640.291301}, {998.113511, 619.128738}}} +{{{1020, 672}, {1020, 651.289307}, {1012.67767, 633.611633}, {998.03302, 618.96698}}} +{{{1020, 672}, {1020, 640.93396}, {998.03302, 618.96698}}} +</div> + +<div id="skpcarrot_is24a"> +{{{1020, 672}, {1020, 651.289307}, {1012.67767, 633.611633}, {998.03302, 618.96698}}} +{{{1020, 672}, {1020, 640.93396}, {998.03302, 618.96698}}} +</div> + +<div id="skpcarrot_is24b"> +{{{1020.08099, 672.161987}, {1020.08051, 651.450988}, {1011.68576, 632.700988}, {998.113511, 619.128738}}} +{{{1020.08099, 672.161987}, {1019.27607, 640.291301}, {998.113511, 619.128738}}} +</div> + +<div id="skpcarrot_is24c"> +{{{{1020.08099,672.161987}, {1020.08002,630.73999}, {986.502014,597.161987}, {945.080994,597.161987}}}, +{{{1020,672}, {1020,640.93396}, {998.03302,618.96698}}}, +</div> + +<div id="skpcarrot_is24d"> +{{{1020.08099, 672.161987}, {1019.27607, 640.291301}, {998.113511, 619.128738}}} +{{{1020, 672}, {1020, 640.93396}, {998.03302, 618.96698}}} +</div> + +<div id="skpcarrot_is24e"> +{{{{1020.08099,672.161987}, {1020.08002,630.73999}, {986.502014,597.161987}, {945.080994,597.161987}}}, +{{{1020.08099, 672.161987}, {1019.27607, 640.291301}, {998.113511, 619.128738}}} +{{{1020, 672}, {1020, 640.93396}, {998.03302, 618.96698}}} +</div> + +<div id="slop1"> +{{{-378.22698974609375, -987.8935546875}, {-47.53326416015625, 482.7139892578125}, {-626.4708251953125, -338.62969970703125}, {-847.94854736328125, -861.42230224609375}}} +{{{-378.61790442466736, -987.49146723747253}, {-282.51787429804051, -556.39065286764685}, {-278.55106873374694, -364.17984985308294}}} +{{{-305.5273847156202, -615.99979442705023}, {-305.04071954345704, -612.87932617187505}}} +</div> + +qT=0.98917687 cT=0.788725084 dist=312.188493 cross=-40759.4852 +<div id="slop2"> +{{{79.5137939,-249.867188}, {260.778931,-561.349854}, {343.375977,-472.657898}, {610.251465,97.8208008}}} +{{{312.993284,-406.178762}, {418.053808,-326.9483}, {610.036929,97.2408578}}} +{{{463.107827,-200.015424}, {602.008878,79.5702581}}} +</div> + +qT=0.0192863061 cT=0.241285225 dist=652.007881 cross=528435.665 +<div id="slop3"> +{{{-895.015015,-523.545288}, {223.166992,-999.644531}, {615.428711,-767.162109}, {605.479736,480.355713}}} +{{{-894.932414,-523.605499}, {-66.4040558,-889.938889}, {278.515212,-667.684158}}} +{{{-207.851881,-740.109296}, {-831.819002,-550.955104}}} +</div> + +qT=0.0245724525 cT=0.231316637 dist=407.103004 cross=-46286.5686 +<div id="slop4"> +{{{876.492798,-849.716187}, {519.430908,-288.374207}, {187.2771,314.324341}, {335.363403,533.086548}}} +{{{876.323133,-849.535824}, {594.868958,-415.229224}, {416.667192,-30.0277669}}} +{{{638.343827,-458.798274}, {849.023879,-807.14691}}} +</div> + +qT=0.000316393778 cT=0.248252634 dist=489.678412 cross=-57352.7653 +<div id="slop5"> +{{{876.492798,-849.716187}, {519.430908,-288.374207}, {187.2771,314.324341}, {335.363403,533.086548}}} +{{{876.147506,-849.184429}, {593.963775,-414.437342}, {416.842819,-30.3791618}}} +{{{622.139843,-430.512844}, {876.135915,-849.166571}}} +</div> + +qT=0.989562776 cT=0.760518485 dist=211.50589 cross=134901.42 +<div id="slop6"> +{{{876.492798,-849.716187}, {519.430908,-288.374207}, {187.2771,314.324341}, {335.363403,533.086548}}} +{{{416.141554,-30.4534414}, {237.846068,356.664216}, {335.719378,533.692585}}} +{{{305.345404,315.701195}, {331.440368,525.591152}}} +</div> + +qT=0.0978245708 cT=0.397465904 dist=959.737748 cross=158093.403 +<div id="slop7"> +{{{895.800171,-222.213013}, {-528.78833,526.47644}, {-967.299927,776.05603}, {-611.228027,319.934814}}} +{{{629.666617,-82.159942}, {-661.943328,620.81113}, {-723.44072,537.121833}}} +{{{-347.560585,421.003177}, {507.062151,-15.707855}}} +</div> + +qT=0.169803964 cT=0.389326872 dist=658.039939 cross=107865.424 +<div id="slop8"> +{{{895.800171,-222.213013}, {-528.78833,526.47644}, {-967.299927,776.05603}, {-611.228027,319.934814}}} +{{{629.536617,-81.7990275}, {-662.457623,620.485316}, {-723.31072,536.760918}}} +{{{-330.996421,413.091598}, {257.080063,117.824582}}} +</div> + +qT=0.0863836955 cT=0.387901231 dist=986.24777 cross=157348.113 +<div id="slop9"> +{{{895.800171,-222.213013}, {-528.78833,526.47644}, {-967.299927,776.05603}, {-611.228027,319.934814}}} +{{{629.248316,-81.8984216}, {-662.339696,620.351182}, {-723.022419,536.860313}}} +{{{-328.058099,411.68229}, {549.399512,-38.5985162}}} +</div> + +qT=0.175359403 cT=0.390420692 dist=640.051938 cross=105488.084 +<div id="slop10"> +{{{895.800171,-222.213013}, {-528.78833,526.47644}, {-967.299927,776.05603}, {-611.228027,319.934814}}} +{{{629.760605,-81.9577046}, {-661.301606,620.029216}, {-723.534707,536.919596}}} +{{{-333.243516,414.168229}, {238.961251,127.37878}}} +</div> + +qT=0.0986412358 cT=0.382365595 dist=921.951857 cross=145651.761 +<div id="slop11"> +{{{895.800171,-222.213013}, {-528.78833,526.47644}, {-967.299927,776.05603}, {-611.228027,319.934814}}} +{{{629.919588,-82.1841825}, {-662.488256,620.04494}, {-723.693691,537.146073}}} +{{{-316.541641,406.142013}, {504.067361,-14.0913644}}} +</div> + +qT=0.146746849 cT=0.391456086 dist=750.006927 cross=123679.094 +<div id="slop12"> +{{{895.800171,-222.213013}, {-528.78833,526.47644}, {-967.299927,776.05603}, {-611.228027,319.934814}}} +{{{629.712675,-82.0366321}, {-661.487948,620.191832}, {-723.486777,536.998523}}} +{{{-335.364605,415.183549}, {334.079508,77.0194322}}} +</div> + +qT=0.00196158131 cT=0.20357489 dist=466.080185 cross=241741.95 +<div id="slop13"> +{{{-627.671509,-359.277039}, {222.414551,-791.598328}, {390.603027,-581.903687}, {-21.7962036,560.33728}}} +{{{-627.675958,-359.282959}, {-52.012535,-659.029798}, {116.967835,-524.756101}}} +{{{-192.427848,-541.033993}, {-622.696937,-361.871356}}} +</div> + +qT=0.948725598 cT=0.744200608 dist=699.694313 cross=179509.878 +<div id="slop14"> +{{{-362.331848,427.292603}, {634.418701,-661.946533}, {438.438599,-626.147278}, {-893.060425,214.243408}}} +{{{259.978301,-393.549091}, {181.692599,-474.452437}, {-892.389834,213.689096}}} +{{{-95.1310032,-267.365579}, {-696.89984,89.6307768}}} +</div> + +qT=0.971677129 cT=0.755306143 dist=771.998962 cross=189468.817 +<div id="slop15"> +{{{-362.331848,427.292603}, {634.418701,-661.946533}, {438.438599,-626.147278}, {-893.060425,214.243408}}} +{{{259.662278,-393.355886}, {181.612843,-473.935297}, {-892.073812,213.495892}}} +{{{-120.438346,-253.451518}, {-782.461182,143.673352}}} +</div> + +qT=0.571797795 cT=0.773951562 dist=495.560209 cross=221091.889 +<div id="slop16"> +{{{447.192383,-883.210205}, {359.794678,-987.765808}, {755.427612,-754.328735}, {963.672119,746.545776}}} +{{{635.795655,-580.726915}, {810.704547,-228.491534}, {963.345162,745.921688}}} +{{{801.470356,-87.7105789}, {646.551495,-558.433498}}} +</div> + +qT=0.579236693 cT=0.782683167 dist=281.750564 cross=65125.1655 +<div id="slop17"> +{{{-931.259155,-883.589966}, {-485.682007,-615.793701}, {-68.5913696,-928.695923}, {431.499268,-810.584778}}} +{{{-177.087049,-804.265618}, {110.452267,-866.525236}, {430.718323,-810.414444}}} +{{{116.080189,-836.904702}, {-164.080748,-807.017753}}} +</div> + +qT=0.0102075348 cT=0.2448024 dist=855.408492 cross=463614.179 +<div id="slop18"> +{{{-867.011292,844.139282}, {-136.156799,-281.244263}, {583.27771,-926.761414}, {998.710205,6.19244385}}} +{{{-866.7221,843.65105}, {-308.756317,-34.8353977}, {183.843514,-346.222431}}} +{{{-336.612013,120.039627}, {-844.283739,808.5112}}} +</div> + +qT=0.473968306 cT=0.266805943 dist=567.851844 cross=-461509.104 +<div id="slop19"> +{{{-867.011292,844.139282}, {-136.156799,-281.244263}, {583.27771,-926.761414}, {998.710205,6.19244385}}} +{{{-867.218781,844.133445}, {-310.496711,-35.0458119}, {184.340195,-346.704825}}} +{{{-290.018097,66.7065093}, {132.536746,-312.639141}}} +</div> + +qT=0.0232589401 cT=0.241085469 dist=789.989464 cross=428119.544 +<div id="slop20"> +{{{-867.011292,844.139282}, {-136.156799,-281.244263}, {583.27771,-926.761414}, {998.710205,6.19244385}}} +{{{-866.942271,843.928587}, {-309.178151,-34.0018497}, {184.063685,-346.499968}}} +{{{-344.507162,129.265381}, {-815.30119,763.644082}}} +</div> + +<div id="skpnamecheap_405"> +{{{141.008835f, 837.9646f}, {141.235291f, 1109.05884f}}} +{{{141, 842}, {141.14502f, 1000}}} +{{{141.14502f, 1000}, {140, 1000}}} +</div> + +<div id="skpwww_dealnews_com_315"> +{{{969.87066650390625, 4279.810546875}, {967.7166748046875, 4260}}} +{{{969.87066650390625, 4279.810546875}, {969.866972698829386, 4279.809233889284769}, {969.88751220703125, 4279.81640625}}} +{{{969.87066650390625, 4279.810546875}, {970, 4281}}} +{{{969.87066650390625, 4279.810546875}, {968.9217161046863112, 4279.473236623693992}, {968.17156982421875, 4278.53564453125}}} +{{{969.8706626470486754, 4279.810469740555163}, {969.8790796016525064, 4279.813461598796493}, {969.88751220703125, 4279.81640625}}} +</div> + +<div id="skpwww_dealnews_com_315_a"> +{{{969.8706626470486754, 4279.810469740555163}, {969.8790796016525064, 4279.813461598796493}, {969.88751220703125, 4279.81640625}}} +{{{969.87066650390625, 4279.810546875}, {969.8790834585100811, 4279.81353873324133}}} +{{{969.88751220703125, 4279.81640625}, {969.8790796016525064, 4279.813461598796493}}} +</div> + +<div id="testQuads60"> +{{{2, 2}, {1.977731304590550021, 1.97773134708404541}, {1.95645439624786377, 1.95546269416809082}}} +{{{2, 2}, {2, 3}}} +{{{2, 2}, {2, 1.960000038146972656}}} +{{{2, 2}, {1.955341815948486328, 1.955341815948486328}}} +</div> + +<div id="testQuads60_a"> +{{{2, 0}, {1, 1}, {2, 2}}} +{{{2, 2}, {0, 0}}} +</div> + +<div id="testQuads60_b"> +{{2,1}, {0,2}, {3,2}}, +{{3,2}, {2,3}}, +{{2,3}, {2,1}}, +{{0,0}, {2,0}}, +{{2,0}, {1,1}, {2,2}}, +{{2,2}, {0,0}}, +</div> + +<div id="skpelpais_com_18"> +{{183,8507}, {552,8506.99023}}, +{{552,8506.99023}, {552,8508}}, +{{552,8508}, {183,8508}}, +{{183,8508}, {183,8507}}, +op intersect +{{183,8508}, {183,8506.99023}}, +{{183,8506.99023}, {552,8507}}, +{{552,8507}, {552,8508}}, +</div> + +<div id="skpwww_cityads_ru_249"> +{{{1000, 10.4003992f}, {1000, 13.3527431f}}} +{{{1000, 13.3527431f}, {999.917603f, 13.2607508f}, {999.82843f, 13.1715727f}}} +{{{1000, 13}, {999.969971f, 37.0299988f}}} +</div> + +<div id="skpwww_maturesupertube_com_21"> + {{{{3.87867975f, 11831.8789f}, {4.7573595f, 11831}, {6, 11831}}}, + {{{2, 11830}, {4.5f, 11832.5f}}}}, +</div> + +<div id="loop1"> +{{1, 4, 2, 6, 0, 5, 4.5f, 4.33333302f +{{2, 6, 0, 5, 4.5f, 4.33333302f, 1, 4 +{{{3, 5}, {2.33333325f, 4.33333349f}, {3.83333325f, 3.83333349f}, {2, 4}}} +{{{2, 4}, {3, 5}, {2.33333325f, 4.33333349f}, {3.83333325f, 3.83333349f}}} +</div> + +<div id="serp1"> +{{{0.55431359440952721, 2.1086271888190544}, {0.1588954256872922, 2.3078315988141811}, {0.57446808656344528, 2.1489361731268914}, {0, 1}}} +{{{0.55431359440952721, 2.1086271888190544}, {0.1588954256872922, 2.3078315988141811}, {0.57446808656344528, 2.1489361731268914}, {0, 1}}} +</div> +<div id="serp2"> +{{{4.0946656649135988, 3.283996994740797}, {4.1983471074380168, 2.1074380165289259}, {4.5454545454545459, 1.3636363636363635}, {4, 3}}} +{{{4.0946656649135988, 3.283996994740797}, {4.1983471074380168, 2.1074380165289259}, {4.5454545454545459, 1.3636363636363635}, {4, 3}}} +</div> +<div id="serp3"> +{{{2.2015477442471254, 1.1371488033013577}, {2.3167674423028526, 0.68323255769714741}, {2.4076432497431028, 0.59235675025689716}, {2, 1}}} +{{{2.2015477442471254, 1.1371488033013577}, {2.3167674423028526, 0.68323255769714741}, {2.4076432497431028, 0.59235675025689716}, {2, 1}}} +</div> + +<div id="skpwww_seopack_blogspot_com_2153"> +{{{924, 245.472672f}, {1143, 247}}} +{{{1000, 246}, {927.340759f, 245.505722f}}} +{{{999.892212f, 246}, {927.340759f, 245.505722f}}} +</div> + +<div id="self1"> +{{{2, 3}, {0, 4}, {3, 2}, {5, 3}}} +{{{2, 3}, {0, 4}, {3, 2}, {5, 3}}} +</div> + +<div id="skpwww_pindosiya_com_99"> +{{{901.0869140625, 547}, {899, 556}}} +{{{900.0235595703125, 551.60284423828125}, {900.06072998046875, 551.29705810546875}, {900.15655517578125, 551.0157470703125}}} +</div> + +<div id="cubicLineMiss1"> +{{{-634.60540771484375, -481.262939453125}, {266.2696533203125, -752.70867919921875}, {-751.8370361328125, -317.37921142578125}, {-969.7427978515625, 824.7255859375}}} +{{{-287.9506133720805678, -557.1376476615772617}, {-285.9506133720805678, -557.1376476615772617}}} +</div> + +<div id="cubicLineMiss2"> +{{{-818.4456787109375, 248.218017578125}, {944.18505859375, -252.2330322265625}, {957.3946533203125, -45.43280029296875}, {-591.766357421875, 868.6187744140625}}} +{{{435.1963493079119871, -16.42683763243891093}, {437.1963493079119871, -16.42683763243891093}}} +</div> + +<div id="cubicLineMiss3"> +{{{-818.4456787109375, 248.218017578125}, {944.18505859375, -252.2330322265625}, {957.3946533203125, -45.43280029296875}, {-591.766357421875, 868.6187744140625}}} +{{{397.5007682490800676, -17.35020084021140008}, {399.5007682490800676, -17.35020084021140008}}} +</div> + +<div id="cubicLineMiss4"> +{{{-652.660888671875, -384.6475830078125}, {-551.7723388671875, -925.5025634765625}, {-321.06658935546875, -813.10345458984375}, {142.6982421875, -47.4503173828125}}} +{{{-478.4372049758064236, -717.868282575075682}, {-476.4372049758064236, -717.868282575075682}}} +</div> + +<div id="cubicLineErr1"> +{{{-954.4322509765625, 827.2216796875}, {-420.24017333984375, -7.80560302734375}, {799.134765625, -971.4295654296875}, {-556.23486328125, 344.400146484375}}} + +{{{58.57411390280688579, -302.8879316712078662}, {60.57411390280688579, -302.8879316712078662}}} +</div> + +<div id="cubicLineErr2"> +{{{-634.60540771484375, -481.262939453125}, {266.2696533203125, -752.70867919921875}, {-751.8370361328125, -317.37921142578125}, {-969.7427978515625, 824.7255859375}}} +{{{-287.95061337208057, -557.13764766157726}, {-285.95061337208057, -557.13764766157726}}} +{{{-308.65463091760211, -549.4520029924679} -308.65463091760211, -569.4520029924679 +</div> + +<div id="skpwww_educationalcraft_com_4"> +{{{974.91998291015625, 1481.7769775390625}, {974.91998291015625, 1481.7760009765625}, {977.3189697265625, 1484.6190185546875}, {975.10699462890625, 1486.97802734375}}} +{{fX=974.91998291015625 fY=1481.7769775390625 }, {fX=974.92071342468262 fY=1481.7972941398621 }} } +</div> + +<div id="skpwww_educationalcraft_com_4a"> +{{{962.10699462890625, 1485.654052734375}, {962.10699462890625, 1485.654052734375}, {960.58502197265625, 1483.595947265625}, {957.53900146484375, 1482.0970458984375}}} +{{{963.21502685546875, 1486.6700439453125}, {962.7449951171875, 1486.6700439453125}, {962.10699462890625, 1485.654052734375}, {962.10699462890625, 1485.654052734375}}} +</div> + +<div id="skpwww_educationalcraft_com_4b"> +{{{980.9000244140625, 1474.3280029296875}, {980.9000244140625, 1474.3280029296875}, {978.89300537109375, 1471.95703125}, {981.791015625, 1469.487060546875}}} +{{{981.791015625, 1469.487060546875}, {981.791015625, 1469.4859619140625}, {983.3580322265625, 1472.72900390625}, {980.9000244140625, 1474.3280029296875}}} +</div> + +<div id="skpwww_aceinfographics_com_106"> +{{{168, 29.6722088f}, {166, 29.6773338f}}} +{{{166.878677f, 29.6750813f}, {167.388f, 29.6763878f}, {168.019989f, 29.6769352f}}} +</div> + +<div id="skpwww_tcmevents_org_13"> +{{{465.84668f, 547.288391f}, {467.274506f, 552.852356f}, {468.506836f, 560.718567f}}} +{{{468.506836f, 560.718567f}, {467.336121f, 553.24585f}, {465.951904f, 547.960144f}} +</div> + +<div id="skpwww_kitcheninspirations_wordpress_com_66"> +{{{60.8333359f, 27820.498f}, {47.1666679f, 27820.5f}}} +{{{60.8333359f, 27820.668f}, {60.8333359f, 27820.498f}}} +{{{47.1666679f, 27820.498f}, {60.8333359f, 27820.5f}}} +{{{60.8333359f, 27820.5f}, {60.8333359f, 27820.668f}}} +</div> + +<div id="skpwww_galaxystwo_com_4"> +{{{10105, 2510}, {10123, 2509.98999f}}} +{{{10105, 2509.98999f}, {10123, 2510}}} +</div> + +<div id="skpwww_wartepop_blogspot_com_br_6"> +{{{124.666672f, 152.333344f}, {125.909309f, 152.333344f}, {126.787994f, 153.309662f}}} +{{fX=124.66666412353516 fY=152.33334350585937 }, {fX=126.78799438476562 fY=153.30966186523437 }} } +{{fX=124.66666412353516 fY=152.33334350585937 }, {fX=127.02368927001953 fY=153.30966186523437 }} } +</div> + +<div id="skpwww_wartepop_blogspot_com_br_6a"> +{{{124.666672f, 152.333344f}, {125.909309f, 152.333344f}, {126.787994f, 153.309662f}}} +{{fX=124.66667175292969 fY=152.33334350585937 }, {fX=126.78799438476562 fY=153.30966186523437 }} } +{{fX=124.66667175292969 fY=152.33334350585937 }, {fX=127.02368927001953 fY=153.30966186523437 }} } +</div> + +<div id="skpcarrot_is24x"> +{{{1020.08099, 672.16198699999995}, {1020.08002, 630.73999000000003}, {986.50201400000003, 597.16198699999995}, {945.08099400000003, 597.16198699999995}}} +{{{1020, 672}, {1020, 640.93395999999996}, {998.03301999999996, 618.96698000000004}}} +</div> + +</div> + +<script type="text/javascript"> + + var testDivs = [ + skpcarrot_is24x, + skpwww_wartepop_blogspot_com_br_6, + skpwww_wartepop_blogspot_com_br_6a, + skpwww_galaxystwo_com_4, + skpwww_kitcheninspirations_wordpress_com_66, + skpwww_tcmevents_org_13, + skpwww_aceinfographics_com_106, + skpwww_educationalcraft_com_4b, + skpwww_educationalcraft_com_4a, + skpwww_educationalcraft_com_4, + cubicLineErr2, + cubicLineErr1, + cubicLineMiss1, + cubicLineMiss2, + cubicLineMiss3, + cubicLineMiss4, + skpwww_pindosiya_com_99, + self1, + skpwww_seopack_blogspot_com_2153, + serp1, + serp2, + serp3, + loop1, + skpwww_maturesupertube_com_21, + skpwww_cityads_ru_249, + skpelpais_com_18, + testQuads60_b, + testQuads60_a, + testQuads60, + skpwww_dealnews_com_315_a, + skpwww_dealnews_com_315, + skpnamecheap_405, + slop1, + slop2, + slop3, + slop4, + slop5, + slop6, + slop7, + slop8, + slop9, + slop10, + slop11, + slop12, + slop13, + slop14, + slop15, + slop16, + slop17, + slop18, + slop19, + slop20, + skpcarrot_is24e, + skpcarrot_is24d, + skpcarrot_is24c, + skpcarrot_is24b, + skpcarrot_is24a, + skpcarrot_is24, + testQuads59, + testQuads45, + testQuads54, + findFirst1, + cubicOp59d, + testQuads22, + cubicOp85d, + cubicOp104, + skpnational_com_au81, + cubicOp35d, + testQuad23, + testQuad22, + testQuad21, + testQuad15, + testQuadratic56, + xop1i, + xOp2i, + xop1u, + skpwww_joomla_org_23, + cubicOp109, + cubicOp106, + cubicOp105, + carpetplanet1, + eldorado1, + cubicOp25i, + cubicOp7d, + simplifyQuadratic27, + simplifyQuadratic56a, + simplifyQuadratic56, + quadratic58again, + simplifyQuadratic58a, + simplifyQuadratic58, + simplifyQuadratic36, + quad89987, + quad8741, + quad8b, + quad8a, + quad208, + quad67242, + quad37226, + quad35237, + quad108, + quad212, + quad3160, + quad640, + quad206, + quad136, + quad113, + quad999, + quad0, + quad179, + quad4a, + quad94, + quad77, + quad22a, + quad14a, + quad809, + quad653, + quad584, + quad413, + quad379, + quad159, + quad232, + quad40, + quad39, + quad38, + quad37, + quad36, + quad35, + quad34, + quad33, + quad32, + quad31, + quad30, + quad29, + quad28, + quad27, + quad26, + quad25, + quad24, + quad23, + quad22, + quad21, + quad20, + quad19, + quad18, + quad17, + quad16, + quad15, + quad14, + quad13, + quad12, + quad11, + cubic2, + cubic1, + quad1, + quad2, + quad3, + quad4, + quad5, + quad6, + quad7, + quad8, + quad9, + quad10, + ]; + + var tests = []; + var testTitles = []; + var testIndex = 0; + var ctx; + var subscale = 1; + var xmin, xmax, ymin, ymax; + var scale; + var initScale; + var mouseX, mouseY; + var mouseDown = false; + var srcLeft, srcTop; + var screenWidth, screenHeight; + var drawnPts; + var curveT = 0; + + var lastX, lastY; + var activeCurve = []; + var activePt; + + var decimal_places = 3; + + var draw_t = false; + var draw_closest_t = false; + var draw_derivative = false; + var draw_endpoints = true; + var draw_midpoint = 0; + var draw_mouse_xy = false; + var draw_order = false; + var draw_point_xy = false; + var draw_ray_intersect = false; + var draw_quarterpoint = 0; + var draw_tangents = 1; + var draw_sortpoint = 0; + var retina_scale = !!window.devicePixelRatio; + + function parse(test, title) { + var curveStrs = test.split("{{"); + var pattern = /-?\d+\.*\d*e?-?\d*/g; + var curves = []; + for (var c in curveStrs) { + var curveStr = curveStrs[c]; + var points = curveStr.match(pattern); + var pts = []; + for (var wd in points) { + var num = parseFloat(points[wd]); + if (isNaN(num)) continue; + pts.push(num); + } + if (pts.length > 2) + curves.push(pts); + } + if (curves.length >= 1) { + tests.push(curves); + testTitles.push(title); + } + } + + function init(test) { + var canvas = document.getElementById('canvas'); + if (!canvas.getContext) return; + ctx = canvas.getContext('2d'); + var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1; + var unscaledWidth = window.innerWidth - 20; + var unscaledHeight = window.innerHeight - 20; + screenWidth = unscaledWidth; + screenHeight = unscaledHeight; + canvas.width = unscaledWidth * resScale; + canvas.height = unscaledHeight * resScale; + canvas.style.width = unscaledWidth + 'px'; + canvas.style.height = unscaledHeight + 'px'; + if (resScale != 1) { + ctx.scale(resScale, resScale); + } + xmin = Infinity; + xmax = -Infinity; + ymin = Infinity; + ymax = -Infinity; + for (var curves in test) { + var curve = test[curves]; + var last = curve.length; + for (var idx = 0; idx < last; idx += 2) { + xmin = Math.min(xmin, curve[idx]); + xmax = Math.max(xmax, curve[idx]); + ymin = Math.min(ymin, curve[idx + 1]); + ymax = Math.max(ymax, curve[idx + 1]); + } + } + xmin -= 1; + var testW = xmax - xmin; + var testH = ymax - ymin; + subscale = 1; + while (testW * subscale < 0.1 && testH * subscale < 0.1) { + subscale *= 10; + } + while (testW * subscale > 10 && testH * subscale > 10) { + subscale /= 10; + } + setScale(xmin, xmax, ymin, ymax); + mouseX = (screenWidth / 2) / scale + srcLeft; + mouseY = (screenHeight / 2) / scale + srcTop; + initScale = scale; + } + + function setScale(x0, x1, y0, y1) { + var srcWidth = x1 - x0; + var srcHeight = y1 - y0; + var usableWidth = screenWidth; + var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10)); + var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10)); + usableWidth -= (xDigits + yDigits) * 10; + usableWidth -= decimal_places * 10; + var hscale = usableWidth / srcWidth; + var vscale = screenHeight / srcHeight; + scale = Math.min(hscale, vscale); + var invScale = 1 / scale; + var sxmin = x0 - invScale * 5; + var symin = y0 - invScale * 10; + var sxmax = x1 + invScale * (6 * decimal_places + 10); + var symax = y1 + invScale * 10; + srcWidth = sxmax - sxmin; + srcHeight = symax - symin; + hscale = usableWidth / srcWidth; + vscale = screenHeight / srcHeight; + scale = Math.min(hscale, vscale); + srcLeft = sxmin; + srcTop = symin; + } + +function dxy_at_t(curve, t) { + var dxy = {}; + if (curve.length == 6) { + var a = t - 1; + var b = 1 - 2 * t; + var c = t; + dxy.x = a * curve[0] + b * curve[2] + c * curve[4]; + dxy.y = a * curve[1] + b * curve[3] + c * curve[5]; + } else if (curve.length == 8) { + var one_t = 1 - t; + var a = curve[0]; + var b = curve[2]; + var c = curve[4]; + var d = curve[6]; + dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t); + a = curve[1]; + b = curve[3]; + c = curve[5]; + d = curve[7]; + dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t); + } + return dxy; +} + + var flt_epsilon = 1.19209290E-07; + + function approximately_zero(A) { + return Math.abs(A) < flt_epsilon; + } + + function approximately_zero_inverse(A) { + return Math.abs(A) > (1 / flt_epsilon); + } + + function quad_real_roots(A, B, C) { + var s = []; + var p = B / (2 * A); + var q = C / A; + if (approximately_zero(A) && (approximately_zero_inverse(p) + || approximately_zero_inverse(q))) { + if (approximately_zero(B)) { + if (C == 0) { + s[0] = 0; + } + return s; + } + s[0] = -C / B; + return s; + } + /* normal form: x^2 + px + q = 0 */ + var p2 = p * p; + if (!approximately_zero(p2 - q) && p2 < q) { + return s; + } + var sqrt_D = 0; + if (p2 > q) { + sqrt_D = Math.sqrt(p2 - q); + } + s[0] = sqrt_D - p; + var flip = -sqrt_D - p; + if (!approximately_zero(s[0] - flip)) { + s[1] = flip; + } + return s; + } + + function cubic_real_roots(A, B, C, D) { + if (approximately_zero(A)) { // we're just a quadratic + return quad_real_roots(B, C, D); + } + if (approximately_zero(D)) { // 0 is one root + var s = quad_real_roots(A, B, C); + for (var i = 0; i < s.length; ++i) { + if (approximately_zero(s[i])) { + return s; + } + } + s.push(0); + return s; + } + if (approximately_zero(A + B + C + D)) { // 1 is one root + var s = quad_real_roots(A, A + B, -D); + for (var i = 0; i < s.length; ++i) { + if (approximately_zero(s[i] - 1)) { + return s; + } + } + s.push(1); + return s; + } + var a, b, c; + var invA = 1 / A; + a = B * invA; + b = C * invA; + c = D * invA; + var a2 = a * a; + var Q = (a2 - b * 3) / 9; + var R = (2 * a2 * a - 9 * a * b + 27 * c) / 54; + var R2 = R * R; + var Q3 = Q * Q * Q; + var R2MinusQ3 = R2 - Q3; + var adiv3 = a / 3; + var r; + var roots = []; + if (R2MinusQ3 < 0) { // we have 3 real roots + var theta = Math.acos(R / Math.sqrt(Q3)); + var neg2RootQ = -2 * Math.sqrt(Q); + r = neg2RootQ * Math.cos(theta / 3) - adiv3; + roots.push(r); + r = neg2RootQ * Math.cos((theta + 2 * Math.PI) / 3) - adiv3; + if (!approximately_zero(roots[0] - r)) { + roots.push(r); + } + r = neg2RootQ * Math.cos((theta - 2 * Math.PI) / 3) - adiv3; + if (!approximately_zero(roots[0] - r) && (roots.length == 1 + || !approximately_zero(roots[1] - r))) { + roots.push(r); + } + } else { // we have 1 real root + var sqrtR2MinusQ3 = Math.sqrt(R2MinusQ3); + var A = Math.abs(R) + sqrtR2MinusQ3; + A = Math.pow(A, 1/3); + if (R > 0) { + A = -A; + } + if (A != 0) { + A += Q / A; + } + r = A - adiv3; + roots.push(r); + if (approximately_zero(R2 - Q3)) { + r = -A / 2 - adiv3; + if (!approximately_zero(s[0] - r)) { + roots.push(r); + } + } + } + return roots; + } + + function approximately_zero_or_more(tValue) { + return tValue >= -flt_epsilon; + } + + function approximately_one_or_less(tValue) { + return tValue <= 1 + flt_epsilon; + } + + function approximately_less_than_zero(tValue) { + return tValue < flt_epsilon; + } + + function approximately_greater_than_one(tValue) { + return tValue > 1 - flt_epsilon; + } + + function add_valid_ts(s) { + var t = []; + nextRoot: + for (var index = 0; index < s.length; ++index) { + var tValue = s[index]; + if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) { + if (approximately_less_than_zero(tValue)) { + tValue = 0; + } else if (approximately_greater_than_one(tValue)) { + tValue = 1; + } + for (var idx2 = 0; idx2 < t.length; ++idx2) { + if (approximately_zero(t[idx2] - tValue)) { + continue nextRoot; + } + } + t.push(tValue); + } + } + return t; + } + + function quad_roots(A, B, C) { + var s = quad_real_roots(A, B, C); + var foundRoots = add_valid_ts(s); + return foundRoots; + } + + function cubic_roots(A, B, C, D) { + var s = cubic_real_roots(A, B, C, D); + var foundRoots = add_valid_ts(s); + return foundRoots; + } + + function ray_curve_intersect(startPt, endPt, curve) { + var adj = endPt[0] - startPt[0]; + var opp = endPt[1] - startPt[1]; + var r = []; + for (var n = 0; n < curve.length / 2; ++n) { + r[n] = (curve[n * 2 + 1] - startPt[1]) * adj - (curve[n * 2] - startPt[0]) * opp; + } + if (curve.length == 6) { + var A = r[2]; + var B = r[1]; + var C = r[0]; + A += C - 2 * B; // A = a - 2*b + c + B -= C; // B = -(b - c) + return quad_roots(A, 2 * B, C); + } + var A = r[3]; // d + var B = r[2] * 3; // 3*c + var C = r[1] * 3; // 3*b + var D = r[0]; // a + A -= D - C + B; // A = -a + 3*b - 3*c + d + B += 3 * D - 2 * C; // B = 3*a - 6*b + 3*c + C -= 3 * D; // C = -3*a + 3*b + return cubic_roots(A, B, C, D); + } + + function x_at_t(curve, t) { + var one_t = 1 - t; + if (curve.length == 4) { + return one_t * curve[0] + t * curve[2]; + } + var one_t2 = one_t * one_t; + var t2 = t * t; + if (curve.length == 6) { + return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4]; + } + var a = one_t2 * one_t; + var b = 3 * one_t2 * t; + var c = 3 * one_t * t2; + var d = t2 * t; + return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6]; + } + + function y_at_t(curve, t) { + var one_t = 1 - t; + if (curve.length == 4) { + return one_t * curve[1] + t * curve[3]; + } + var one_t2 = one_t * one_t; + var t2 = t * t; + if (curve.length == 6) { + return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5]; + } + var a = one_t2 * one_t; + var b = 3 * one_t2 * t; + var c = 3 * one_t * t2; + var d = t2 * t; + return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7]; + } + + function drawPointAtT(curve) { + var x = x_at_t(curve, curveT); + var y = y_at_t(curve, curveT); + drawPoint(x, y); + } + + function drawLine(x1, y1, x2, y2) { + ctx.beginPath(); + ctx.moveTo((x1 - srcLeft) * scale, + (y1 - srcTop) * scale); + ctx.lineTo((x2 - srcLeft) * scale, + (y2 - srcTop) * scale); + ctx.stroke(); + } + + function drawPoint(px, py) { + for (var pts = 0; pts < drawnPts.length; pts += 2) { + var x = drawnPts[pts]; + var y = drawnPts[pts + 1]; + if (px == x && py == y) { + return; + } + } + drawnPts.push(px); + drawnPts.push(py); + var _px = (px - srcLeft) * scale; + var _py = (py - srcTop) * scale; + ctx.beginPath(); + ctx.arc(_px, _py, 3, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.stroke(); + if (draw_point_xy) { + var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places); + ctx.font = "normal 10px Arial"; + ctx.textAlign = "left"; + ctx.fillStyle = "black"; + ctx.fillText(label, _px + 5, _py); + } + } + + function drawPointSolid(px, py) { + drawPoint(px, py); + ctx.fillStyle = "rgba(0,0,0, 0.4)"; + ctx.fill(); + } + + function crossPt(origin, pt1, pt2) { + return ((pt1[0] - origin[0]) * (pt2[1] - origin[1]) + - (pt1[1] - origin[1]) * (pt2[0] - origin[0])) > 0 ? 0 : 1; + } + + // may not work well for cubics + function curveClosestT(curve, x, y) { + var closest = -1; + var closestDist = Infinity; + var l = Infinity, t = Infinity, r = -Infinity, b = -Infinity; + for (var i = 0; i < 16; ++i) { + var testX = x_at_t(curve, i / 16); + l = Math.min(testX, l); + r = Math.max(testX, r); + var testY = y_at_t(curve, i / 16); + t = Math.min(testY, t); + b = Math.max(testY, b); + var dx = testX - x; + var dy = testY - y; + var dist = dx * dx + dy * dy; + if (closestDist > dist) { + closestDist = dist; + closest = i; + } + } + var boundsX = r - l; + var boundsY = b - t; + var boundsDist = boundsX * boundsX + boundsY * boundsY; + if (closestDist > boundsDist) { + return -1; + } + console.log("closestDist = " + closestDist + " boundsDist = " + boundsDist + + " t = " + closest / 16); + return closest / 16; + } + + function draw(test, title) { + ctx.font = "normal 50px Arial"; + ctx.textAlign = "left"; + ctx.fillStyle = "rgba(0,0,0, 0.1)"; + ctx.fillText(title, 50, 50); + ctx.font = "normal 10px Arial"; + // ctx.lineWidth = "1.001"; "0.999"; + var hullStarts = []; + var hullEnds = []; + var midSpokes = []; + var midDist = []; + var origin = []; + var shortSpokes = []; + var shortDist = []; + var sweeps = []; + drawnPts = []; + for (var curves in test) { + var curve = test[curves]; + origin.push(curve[0]); + origin.push(curve[1]); + var startPt = []; + startPt.push(curve[2]); + startPt.push(curve[3]); + hullStarts.push(startPt); + var endPt = []; + if (curve.length == 4) { + endPt.push(curve[2]); + endPt.push(curve[3]); + } else if (curve.length == 6) { + endPt.push(curve[4]); + endPt.push(curve[5]); + } else if (curve.length == 8) { + endPt.push(curve[6]); + endPt.push(curve[7]); + } + hullEnds.push(endPt); + var sweep = crossPt(origin, startPt, endPt); + sweeps.push(sweep); + var midPt = []; + midPt.push(x_at_t(curve, 0.5)); + midPt.push(y_at_t(curve, 0.5)); + midSpokes.push(midPt); + var shortPt = []; + shortPt.push(x_at_t(curve, 0.25)); + shortPt.push(y_at_t(curve, 0.25)); + shortSpokes.push(shortPt); + var dx = midPt[0] - origin[0]; + var dy = midPt[1] - origin[1]; + var dist = Math.sqrt(dx * dx + dy * dy); + midDist.push(dist); + dx = shortPt[0] - origin[0]; + dy = shortPt[1] - origin[1]; + dist = Math.sqrt(dx * dx + dy * dy); + shortDist.push(dist); + } + var intersect = []; + var useIntersect = false; + var maxWidth = Math.max(xmax - xmin, ymax - ymin); + for (var curves in test) { + var curve = test[curves]; + if (curve.length == 6 || curve.length == 8) { + var opp = curves == 0 || curves == 1 ? 0 : 1; + var sects = ray_curve_intersect(origin, hullEnds[opp], curve); + intersect.push(sects); + if (sects.length > 1) { + var intersection = sects[0]; + if (intersection == 0) { + intersection = sects[1]; + } + var ix = x_at_t(curve, intersection) - origin[0]; + var iy = y_at_t(curve, intersection) - origin[1]; + var ex = hullEnds[opp][0] - origin[0]; + var ey = hullEnds[opp][1] - origin[1]; + if (ix * ex >= 0 && iy * ey >= 0) { + var iDist = Math.sqrt(ix * ix + iy * iy); + var eDist = Math.sqrt(ex * ex + ey * ey); + var delta = Math.abs(iDist - eDist) / maxWidth; + if (delta > (curve.length == 6 ? 1e-5 : 1e-4)) { + useIntersect ^= true; + } + } + } + } + } + var midLeft = curves != 0 ? crossPt(origin, midSpokes[0], midSpokes[1]) : 0; + var firstInside; + if (useIntersect) { + var sect1 = intersect[0].length > 1; + var sIndex = sect1 ? 0 : 1; + var sects = intersect[sIndex]; + var intersection = sects[0]; + if (intersection == 0) { + intersection = sects[1]; + } + var curve = test[sIndex]; + var ix = x_at_t(curve, intersection) - origin[0]; + var iy = y_at_t(curve, intersection) - origin[1]; + var opp = sect1 ? 1 : 0; + var ex = hullEnds[opp][0] - origin[0]; + var ey = hullEnds[opp][1] - origin[1]; + var iDist = ix * ix + iy * iy; + var eDist = ex * ex + ey * ey; + firstInside = (iDist > eDist) ^ (sIndex == 0) ^ sweeps[0]; +// console.log("iDist=" + iDist + " eDist=" + eDist + " sIndex=" + sIndex + // + " sweeps[0]=" + sweeps[0]); + } else { + // console.log("midLeft=" + midLeft); + firstInside = midLeft != 0; + } + var shorter = midDist[1] < midDist[0]; + var shortLeft = shorter ? crossPt(origin, shortSpokes[0], midSpokes[1]) + : crossPt(origin, midSpokes[0], shortSpokes[1]); + var startCross = crossPt(origin, hullStarts[0], hullStarts[1]); + var disallowShort = midLeft == startCross && midLeft == sweeps[0] + && midLeft == sweeps[1]; + + // console.log("midLeft=" + midLeft + " startCross=" + startCross); + var intersectIndex = 0; + for (var curves in test) { + var curve = test[curves]; + if (curve.length != 4 && curve.length != 6 && curve.length != 8) { + continue; + } + ctx.lineWidth = 1; + if (draw_tangents != 0) { + if (firstInside == curves) { + ctx.strokeStyle = "rgba(255,0,0, 0.3)"; + } else { + ctx.strokeStyle = "rgba(0,0,255, 0.3)"; + } + drawLine(curve[0], curve[1], curve[2], curve[3]); + if (draw_tangents != 2) { + if (curve.length > 4) drawLine(curve[2], curve[3], curve[4], curve[5]); + if (curve.length > 6) drawLine(curve[4], curve[5], curve[6], curve[7]); + } + if (draw_tangents != 1) { + if (curve.length == 6) drawLine(curve[0], curve[1], curve[4], curve[5]); + if (curve.length == 8) drawLine(curve[0], curve[1], curve[6], curve[7]); + } + } + ctx.beginPath(); + ctx.moveTo((curve[0] - srcLeft) * scale, (curve[1] - srcTop) * scale); + if (curve.length == 4) { + ctx.lineTo((curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale); + } else if (curve.length == 6) { + ctx.quadraticCurveTo( + (curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale, + (curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale); + } else { + ctx.bezierCurveTo( + (curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale, + (curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale, + (curve[6] - srcLeft) * scale, (curve[7] - srcTop) * scale); + } + if (firstInside == curves) { + ctx.strokeStyle = "rgba(255,0,0, 1)"; + } else { + ctx.strokeStyle = "rgba(0,0,255, 1)"; + } + ctx.stroke(); + if (draw_endpoints) { + drawPoint(curve[0], curve[1]); + drawPoint(curve[2], curve[3]); + if (curve.length > 4) drawPoint(curve[4], curve[5]); + if (curve.length > 6) drawPoint(curve[6], curve[7]); + } + if (draw_midpoint != 0) { + if ((curves == 0) == (midLeft == 0)) { + ctx.strokeStyle = "rgba(0,180,127, 0.6)"; + } else { + ctx.strokeStyle = "rgba(127,0,127, 0.6)"; + } + var midX = x_at_t(curve, 0.5); + var midY = y_at_t(curve, 0.5); + drawPointSolid(midX, midY); + if (draw_midpoint > 1) { + drawLine(curve[0], curve[1], midX, midY); + } + } + if (draw_quarterpoint != 0) { + if ((curves == 0) == (shortLeft == 0)) { + ctx.strokeStyle = "rgba(0,191,63, 0.6)"; + } else { + ctx.strokeStyle = "rgba(63,0,191, 0.6)"; + } + var midT = (curves == 0) == shorter ? 0.25 : 0.5; + var midX = x_at_t(curve, midT); + var midY = y_at_t(curve, midT); + drawPointSolid(midX, midY); + if (draw_quarterpoint > 1) { + drawLine(curve[0], curve[1], midX, midY); + } + } + if (draw_sortpoint != 0) { + if ((curves == 0) == ((disallowShort == -1 ? midLeft : shortLeft) == 0)) { + ctx.strokeStyle = "rgba(0,155,37, 0.6)"; + } else { + ctx.strokeStyle = "rgba(37,0,155, 0.6)"; + } + var midT = (curves == 0) == shorter && disallowShort != curves ? 0.25 : 0.5; + console.log("curves=" + curves + " disallowShort=" + disallowShort + + " midLeft=" + midLeft + " shortLeft=" + shortLeft + + " shorter=" + shorter + " midT=" + midT); + var midX = x_at_t(curve, midT); + var midY = y_at_t(curve, midT); + drawPointSolid(midX, midY); + if (draw_sortpoint > 1) { + drawLine(curve[0], curve[1], midX, midY); + } + } + if (draw_ray_intersect != 0) { + ctx.strokeStyle = "rgba(75,45,199, 0.6)"; + if (curve.length == 6 || curve.length == 8) { + var intersections = intersect[intersectIndex]; + for (var i in intersections) { + var intersection = intersections[i]; + var x = x_at_t(curve, intersection); + var y = y_at_t(curve, intersection); + drawPointSolid(x, y); + if (draw_ray_intersect > 1) { + drawLine(curve[0], curve[1], x, y); + } + } + } + ++intersectIndex; + } + if (draw_order) { + var px = x_at_t(curve, 0.75); + var py = y_at_t(curve, 0.75); + var _px = (px - srcLeft) * scale; + var _py = (py - srcTop) * scale; + ctx.beginPath(); + ctx.arc(_px, _py, 15, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fillStyle = "white"; + ctx.fill(); + if (firstInside == curves) { + ctx.strokeStyle = "rgba(255,0,0, 1)"; + ctx.fillStyle = "rgba(255,0,0, 1)"; + } else { + ctx.strokeStyle = "rgba(0,0,255, 1)"; + ctx.fillStyle = "rgba(0,0,255, 1)"; + } + ctx.stroke(); + ctx.font = "normal 16px Arial"; + ctx.textAlign = "center"; + ctx.fillText(parseInt(curves) + 1, _px, _py + 5); + } + if (draw_closest_t) { + var t = curveClosestT(curve, mouseX, mouseY); + if (t >= 0) { + var x = x_at_t(curve, t); + var y = y_at_t(curve, t); + drawPointSolid(x, y); + } + } + if (!approximately_zero(scale - initScale)) { + ctx.font = "normal 20px Arial"; + ctx.fillStyle = "rgba(0,0,0, 0.3)"; + ctx.textAlign = "right"; + ctx.fillText(scale.toFixed(decimal_places) + 'x', + screenWidth - 10, screenHeight - 5); + } + if (draw_t) { + drawPointAtT(curve); + } + } + if (draw_t) { + drawCurveTControl(); + } + } + + function drawCurveTControl() { + ctx.lineWidth = 2; + ctx.strokeStyle = "rgba(0,0,0, 0.3)"; + ctx.beginPath(); + ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80); + ctx.stroke(); + var ty = 40 + curveT * (screenHeight - 80); + ctx.beginPath(); + ctx.moveTo(screenWidth - 80, ty); + ctx.lineTo(screenWidth - 85, ty - 5); + ctx.lineTo(screenWidth - 85, ty + 5); + ctx.lineTo(screenWidth - 80, ty); + ctx.fillStyle = "rgba(0,0,0, 0.6)"; + ctx.fill(); + var num = curveT.toFixed(decimal_places); + ctx.font = "normal 10px Arial"; + ctx.textAlign = "left"; + ctx.fillText(num, screenWidth - 78, ty); + } + + function ptInTControl() { + var e = window.event; + var tgt = e.target || e.srcElement; + var left = tgt.offsetLeft; + var top = tgt.offsetTop; + var x = (e.clientX - left); + var y = (e.clientY - top); + if (x < screenWidth - 80 || x > screenWidth - 50) { + return false; + } + if (y < 40 || y > screenHeight - 80) { + return false; + } + curveT = (y - 40) / (screenHeight - 120); + if (curveT < 0 || curveT > 1) { + throw "stop execution"; + } + return true; + } + + function drawTop() { + init(tests[testIndex]); + redraw(); + } + + function redraw() { + ctx.beginPath(); + ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.fillStyle = "white"; + ctx.fill(); + draw(tests[testIndex], testTitles[testIndex]); + } + + function doKeyPress(evt) { + var char = String.fromCharCode(evt.charCode); + switch (char) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + decimal_places = char - '0'; + redraw(); + break; + case '-': + scale /= 2; + calcLeftTop(); + redraw(); + break; + case '=': + case '+': + scale *= 2; + calcLeftTop(); + redraw(); + break; + case 'c': + drawTop(); + break; + case 'd': + var test = tests[testIndex]; + var testClone = []; + for (var curves in test) { + var c = test[curves]; + var cClone = []; + for (var index = 0; index < c.length; ++index) { + cClone.push(c[index]); + } + testClone.push(cClone); + } + tests.push(testClone); + testTitles.push(testTitles[testIndex] + " copy"); + testIndex = tests.length - 1; + redraw(); + break; + case 'e': + draw_endpoints ^= true; + redraw(); + break; + case 'f': + draw_derivative ^= true; + redraw(); + break; + case 'i': + draw_ray_intersect = (draw_ray_intersect + 1) % 3; + redraw(); + break; + case 'l': + var test = tests[testIndex]; + console.log("<div id=\"" + testTitles[testIndex] + "\" >"); + for (var curves in test) { + var c = test[curves]; + var s = "{{"; + for (var i = 0; i < c.length; i += 2) { + s += "{"; + s += c[i] + "," + c[i + 1]; + s += "}"; + if (i + 2 < c.length) { + s += ", "; + } + } + console.log(s + "}},"); + } + console.log("</div>"); + break; + case 'm': + draw_midpoint = (draw_midpoint + 1) % 3; + redraw(); + break; + case 'N': + testIndex += 9; + case 'n': + testIndex = (testIndex + 1) % tests.length; + drawTop(); + break; + case 'o': + draw_order ^= true; + redraw(); + break; + case 'P': + testIndex -= 9; + case 'p': + if (--testIndex < 0) + testIndex = tests.length - 1; + drawTop(); + break; + case 'q': + draw_quarterpoint = (draw_quarterpoint + 1) % 3; + redraw(); + break; + case 'r': + for (var i = 0; i < testDivs.length; ++i) { + var title = testDivs[i].id.toString(); + if (title == testTitles[testIndex]) { + var str = testDivs[i].firstChild.data; + parse(str, title); + var original = tests.pop(); + testTitles.pop(); + tests[testIndex] = original; + break; + } + } + redraw(); + break; + case 's': + draw_sortpoint = (draw_sortpoint + 1) % 3; + redraw(); + break; + case 't': + draw_t ^= true; + redraw(); + break; + case 'u': + draw_closest_t ^= true; + redraw(); + break; + case 'v': + draw_tangents = (draw_tangents + 1) % 4; + redraw(); + break; + case 'x': + draw_point_xy ^= true; + redraw(); + break; + case 'y': + draw_mouse_xy ^= true; + redraw(); + break; + case '\\': + retina_scale ^= true; + drawTop(); + break; + } + } + + function doKeyDown(evt) { + var char = evt.keyCode; + var preventDefault = false; + switch (char) { + case 37: // left arrow + if (evt.shiftKey) { + testIndex -= 9; + } + if (--testIndex < 0) + testIndex = tests.length - 1; + drawTop(); + preventDefault = true; + break; + case 39: // right arrow + if (evt.shiftKey) { + testIndex += 9; + } + if (++testIndex >= tests.length) + testIndex = 0; + drawTop(); + preventDefault = true; + break; + } + if (preventDefault) { + evt.preventDefault(); + return false; + } + return true; + } + + function calcXY() { + var e = window.event; + var tgt = e.target || e.srcElement; + var left = tgt.offsetLeft; + var top = tgt.offsetTop; + mouseX = (e.clientX - left) / scale + srcLeft; + mouseY = (e.clientY - top) / scale + srcTop; + } + + function calcLeftTop() { + srcLeft = mouseX - screenWidth / 2 / scale; + srcTop = mouseY - screenHeight / 2 / scale; + } + + function handleMouseClick() { + if (!draw_t || !ptInTControl()) { + calcXY(); + } else { + redraw(); + } + } + + function initDown() { + var test = tests[testIndex]; + var bestDistance = 1000000; + activePt = -1; + for (var curves in test) { + var testCurve = test[curves]; + if (testCurve.length != 4 && testCurve.length != 6 && testCurve.length != 8) { + continue; + } + for (var i = 0; i < testCurve.length; i += 2) { + var testX = testCurve[i]; + var testY = testCurve[i + 1]; + var dx = testX - mouseX; + var dy = testY - mouseY; + var dist = dx * dx + dy * dy; + if (dist > bestDistance) { + continue; + } + activeCurve = testCurve; + activePt = i; + bestDistance = dist; + } + } + if (activePt >= 0) { + lastX = mouseX; + lastY = mouseY; + } + } + + function handleMouseOver() { + calcXY(); + if (draw_mouse_xy) { + var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places); + ctx.beginPath(); + ctx.rect(300, 100, num.length * 6, 10); + ctx.fillStyle = "white"; + ctx.fill(); + ctx.font = "normal 10px Arial"; + ctx.fillStyle = "black"; + ctx.textAlign = "left"; + ctx.fillText(num, 300, 108); + } + if (!mouseDown) { + activePt = -1; + return; + } + if (activePt < 0) { + initDown(); + return; + } + var deltaX = mouseX - lastX; + var deltaY = mouseY - lastY; + lastX = mouseX; + lastY = mouseY; + if (activePt == 0) { + var test = tests[testIndex]; + for (var curves in test) { + var testCurve = test[curves]; + testCurve[0] += deltaX; + testCurve[1] += deltaY; + } + } else { + activeCurve[activePt] += deltaX; + activeCurve[activePt + 1] += deltaY; + } + redraw(); + } + + function start() { + for (var i = 0; i < testDivs.length; ++i) { + var title = testDivs[i].id.toString(); + var str = testDivs[i].firstChild.data; + parse(str, title); + } + drawTop(); + window.addEventListener('keypress', doKeyPress, true); + window.addEventListener('keydown', doKeyDown, true); + window.onresize = function () { + drawTop(); + } + } + +</script> +</head> + +<body onLoad="start();"> + +<canvas id="canvas" width="750" height="500" + onmousedown="mouseDown = true" + onmouseup="mouseDown = false" + onmousemove="handleMouseOver()" + onclick="handleMouseClick()" + ></canvas > +</body> +</html>
\ No newline at end of file diff --git a/chromium/third_party/skia/tools/pathops_visualizer.htm b/chromium/third_party/skia/tools/pathops_visualizer.htm new file mode 100644 index 00000000000..9872b386823 --- /dev/null +++ b/chromium/third_party/skia/tools/pathops_visualizer.htm @@ -0,0 +1,3330 @@ +<html> +<head> +<div height="0" hidden="true"> + +<div id="skpwww_argus_presse_fr_41"> + RunTestSet [skpwww_argus_presse_fr_41] + +{{1000,343}, {165,343}}, +{{165,343}, {165,364.869873}}, +{{165,364.869873}, {1000,364.869873}}, +{{1000,364.869873}, {1000,343}}, +op intersect +{{165,343.000031}, {1000,343.000031}}, +{{1000,343.000031}, {1000,364.869904}}, +{{1000,364.869904}, {165,364.869904}}, +{{165,364.869904}, {165,343.000031}}, +debugShowLineIntersection wtTs[0]=0 {{165,343}, {165,364.869873}} {{165,343}} wnTs[0]=1 {{1000,343}, {165,343}} +debugShowLineIntersection wtTs[0]=1 {{1000,364.869873}, {1000,343}} {{1000,343}} wnTs[0]=0 {{1000,343}, {165,343}} +debugShowLineIntersection wtTs[0]=0 {{165,364.869873}, {1000,364.869873}} {{165,364.869873}} wnTs[0]=1 {{165,343}, {165,364.869873}} +debugShowLineIntersection wtTs[0]=0 {{1000,364.869873}, {1000,343}} {{1000,364.869873}} wnTs[0]=1 {{165,364.869873}, {1000,364.869873}} +debugShowLineIntersection wtTs[0]=0 {{165,343.000031}, {1000,343.000031}} {{165,343}} wtTs[1]=1 {{1000,343}} wnTs[0]=1 {{1000,343}, {165,343}} wnTs[1]=0 +debugShowLineIntersection wtTs[0]=0 {{1000,343.000031}, {1000,364.869904}} {{1000,343.000031}} wnTs[0]=0 {{1000,343}, {165,343}} +debugShowLineIntersection wtTs[0]=1 {{165,364.869904}, {165,343.000031}} {{165,343.000031}} wnTs[0]=1 {{1000,343}, {165,343}} +debugShowLineIntersection wtTs[0]=0 {{165,343.000031}, {1000,343.000031}} {{165,343}} wnTs[0]=0 {{165,343}, {165,364.869873}} +debugShowLineIntersection wtTs[0]=1 {{1000,364.869904}, {165,364.869904}} {{165,364.869873}} wnTs[0]=1 {{165,343}, {165,364.869873}} +debugShowLineIntersection wtTs[0]=0 {{165,364.869904}, {165,343.000031}} {{165,364.869904}} wtTs[1]=1 {{165,343.000031}} wnTs[0]=1 {{165,343}, {165,364.869873}} wnTs[1]=1.39541634e-006 +debugShowLineIntersection wtTs[0]=1 {{1000,343.000031}, {1000,364.869904}} {{1000,364.869904}} wnTs[0]=1 {{165,364.869873}, {1000,364.869873}} +debugShowLineIntersection wtTs[0]=0 {{1000,364.869904}, {165,364.869904}} {{1000,364.869873}} wtTs[1]=1 {{165,364.869873}} wnTs[0]=1 {{165,364.869873}, {1000,364.869873}} wnTs[1]=0 +debugShowLineIntersection wtTs[0]=0 {{165,364.869904}, {165,343.000031}} {{165,364.869904}} wnTs[0]=0 {{165,364.869873}, {1000,364.869873}} +debugShowLineIntersection wtTs[0]=1 {{165,343.000031}, {1000,343.000031}} {{1000,343}} wnTs[0]=1 {{1000,364.869873}, {1000,343}} +debugShowLineIntersection wtTs[0]=0 {{1000,343.000031}, {1000,364.869904}} {{1000,343.000031}} wtTs[1]=1 {{1000,364.869904}} wnTs[0]=0.999999 {{1000,364.869873}, {1000,343}} wnTs[1]=0 +debugShowLineIntersection wtTs[0]=0 {{1000,364.869904}, {165,364.869904}} {{1000,364.869873}} wnTs[0]=0 {{1000,364.869873}, {1000,343}} +debugShowLineIntersection wtTs[0]=0 {{1000,343.000031}, {1000,364.869904}} {{1000,343.000031}} wnTs[0]=1 {{165,343.000031}, {1000,343.000031}} +debugShowLineIntersection wtTs[0]=1 {{165,364.869904}, {165,343.000031}} {{165,343.000031}} wnTs[0]=0 {{165,343.000031}, {1000,343.000031}} +debugShowLineIntersection wtTs[0]=0 {{1000,364.869904}, {165,364.869904}} {{1000,364.869904}} wnTs[0]=1 {{1000,343.000031}, {1000,364.869904}} +debugShowLineIntersection wtTs[0]=0 {{165,364.869904}, {165,343.000031}} {{165,364.869904}} wnTs[0]=1 {{1000,364.869904}, {165,364.869904}} +SkOpSegment::debugShowTs - id=0 [o=3,5 t=0 1000,343.000031 w=1 o=0] [o=7,1 t=1 165,343 w=1 o=0] +SkOpSegment::debugShowTs o id=4 [o=7,1 t=0 165,343 w=1 o=0] [o=3,5 t=1 1000,343.000031 w=1 o=0] operand +SkOpSegment::debugShowTs + id=0 [o=3,5 t=0 1000,343.000031 w=1 o=0] [o=7,1 t=1 165,343 w=1 o=0] +SkOpSegment::debugShowTs o id=4 [o=7,1 t=0 165,343 w=1 o=0] [o=3,5 t=1 1000,343.000031 w=1 o=0] operand +SkOpSegment::debugShowTs - id=1 [o=4,0 t=0 165,343 w=1 o=0] [o=6,2 t=1 165,364.869873 w=1 o=0] +SkOpSegment::debugShowTs o id=7 [o=6,2 t=0 165,364.869904 w=1 o=0] [o=4,0 t=1 165,343.000031 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=1 1.39541634e-006 other=7 1 +SkOpSegment::addTPair addTPair this=7 0 other=1 1 +SkOpSegment::debugShowTs + id=1 [o=4,0 t=0 165,343 w=1 o=0] [o=7 t=1.4e-006 165,343.000031 w=1 o=0] [o=7,6,2 t=1 165,364.869873 w=1 o=0] +SkOpSegment::debugShowTs o id=7 [o=1,6,2 t=0 165,364.869904 w=1 o=0] [o=1,4,0 t=1 165,343.000031 w=1 o=0] operand +SkOpSegment::debugShowTs - id=2 [o=1,7 t=0 165,364.869904 w=1 o=0] [o=5,3 t=1 1000,364.869873 w=1 o=0] +SkOpSegment::debugShowTs o id=6 [o=5,3 t=0 1000,364.869873 w=1 o=0] [o=1,7 t=1 165,364.869904 w=1 o=0] operand +SkOpSegment::debugShowTs + id=2 [o=1,7 t=0 165,364.869904 w=1 o=0] [o=5,3 t=1 1000,364.869873 w=1 o=0] +SkOpSegment::debugShowTs o id=6 [o=5,3 t=0 1000,364.869873 w=1 o=0] [o=1,7 t=1 165,364.869904 w=1 o=0] operand +SkOpSegment::debugShowTs - id=3 [o=6,2 t=0 1000,364.869873 w=1 o=0] [o=4,0 t=1 1000,343 w=1 o=0] +SkOpSegment::debugShowTs o id=5 [o=4,0 t=0 1000,343.000031 w=1 o=0] [o=6,2 t=1 1000,364.869904 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=3 0 other=5 1 +SkOpSegment::addTPair addTPair this=5 0 other=3 0.999998605 +SkOpSegment::debugShowTs + id=3 [o=6,2,5 t=0 1000,364.869904 w=1 o=0] [o=5 t=1 1000,343.000031 w=1 o=0] [o=4,0 t=1 1000,343 w=1 o=0] +SkOpSegment::debugShowTs o id=5 [o=3,4,0 t=0 1000,343.000031 w=1 o=0] [o=3,6,2 t=1 1000,364.869904 w=1 o=0] operand +SkOpContour::calcCoincidentWinding count=4 +SkOpSegment::debugShowTs p id=0 [o=3,5 t=0 1000,343.000031 w=1 o=-1] [o=7,1 t=1 165,343 w=1 o=0] +SkOpSegment::debugShowTs o id=4 [o=7,1 t=0 165,343 w=0 o=0] [o=3,5 t=1 1000,343.000031 w=1 o=0] operand done +SkOpSegment::debugShowTs p id=1 [o=4,0 t=0 165,343 w=1 o=0] [o=7 t=1.4e-006 165,343.000031 w=1 o=-1] [o=7,6,2 t=1 165,364.869873 w=1 o=0] +SkOpSegment::debugShowTs o id=7 [o=1,6,2 t=0 165,364.869904 w=0 o=0] [o=1,4,0 t=1 165,343.000031 w=1 o=0] operand done +SkOpSegment::debugShowTs p id=2 [o=1,7 t=0 165,364.869904 w=1 o=-1] [o=5,3 t=1 1000,364.869873 w=1 o=0] +SkOpSegment::debugShowTs o id=6 [o=5,3 t=0 1000,364.869873 w=0 o=0] [o=1,7 t=1 165,364.869904 w=1 o=0] operand done +SkOpSegment::debugShowTs p id=3 [o=6,2,5 t=0 1000,364.869904 w=1 o=-1] [o=5 t=1 1000,343.000031 w=1 o=0] [o=4,0 t=1 1000,343 w=1 o=0] +SkOpSegment::debugShowTs o id=5 [o=3,4,0 t=0 1000,343.000031 w=0 o=0] [o=3,6,2 t=1 1000,364.869904 w=1 o=0] operand done +SkOpSegment::addTPair addTPair this=0 0 other=4 1 +SkOpSegment::addTPair addTPair this=0 1 other=4 0 +SkOpSegment::addTPair addTPair this=6 1 other=2 0 +SkOpSegment::addTPair addTPair duplicate this=2 0 other=6 1 +SkOpSegment::addTPair addTPair this=2 1 other=6 0 +SkOpContour::joinCoincidence count=4 +SkOpSegment::sortAngles [0] tStart=0 [0] +SkOpSegment::sortAngles [0] tStart=1 [5] +SkOpSegment::sortAngles [1] tStart=1.39541634e-006 [2] +SkOpSegment::sortAngles [1] tStart=1 [5] +SkOpSegment::sortAngles [2] tStart=1 [5] +SkOpSegment::sortAngles [3] tStart=0.999998605 [3] +SkOpSegment::debugShowActiveSpans id=0 (1000,343 165,343) t=0 (1000,343) tEnd=1 other=3 otherT=1 otherIndex=5 windSum=? windValue=1 oppValue=-1 +SkOpSegment::debugShowActiveSpans id=1 (165,343 165,364.869873) t=1.39541634e-006 (165,343.000031) tEnd=1 other=7 otherT=1 otherIndex=3 windSum=? windValue=1 oppValue=-1 +SkOpSegment::debugShowActiveSpans id=2 (165,364.869873 1000,364.869873) t=0 (165,364.869873) tEnd=1 other=6 otherT=1 otherIndex=3 windSum=? windValue=1 oppValue=-1 +SkOpSegment::debugShowActiveSpans id=3 (1000,364.869873 1000,343) t=0 (1000,364.869873) tEnd=0.999998605 other=6 otherT=0 otherIndex=2 windSum=? windValue=1 oppValue=-1 +Assemble + +</div> + +</div> + +<script type="text/javascript"> + +var testDivs = [ + skpwww_argus_presse_fr_41, +]; + +var decimal_places = 3; // make this 3 to show more precision + +var tests = []; +var testLines = []; +var testTitles = []; +var testIndex = 0; +var ctx; + +var xmin, xmax, focusXmin, focusXmax; +var ymin, ymax, focusYmin, focusYmax; +var scale; +var mouseX, mouseY; +var srcLeft, srcTop; +var screenWidth, screenHeight; +var drawnPts, drawnLines, drawnQuads, drawnCubics; +var curveT = 0; + +var pt_labels = 2; +var collect_bounds = false; +var control_lines = 0; +var curve_t = false; +var debug_xy = 1; +var focus_enabled = false; +var focus_on_selection = false; +var step_limit = 0; +var draw_active = false; +var draw_add = false; +var draw_angle = 0; +var draw_deriviatives = 0; +var draw_hints = false; +var draw_hodo = 0; +var draw_id = false; +var draw_intersection = 0; +var draw_intersectT = false; +var draw_legend = true; +var draw_log = false; +var draw_mark = false; +var draw_midpoint = false; +var draw_op = 0; +var draw_sequence = false; +var draw_sort = 0; +var draw_path = 3; +var draw_computed = 0; +var retina_scale = !!window.devicePixelRatio; + +var activeCount = 0; +var addCount = 0; +var angleCount = 0; +var opCount = 0; +var sectCount = 0; +var sortCount = 0; +var markCount = 0; +var activeMax = 0; +var addMax = 0; +var angleMax = 0; +var sectMax = 0; +var sectMax2 = 0; +var sortMax = 0; +var markMax = 0; +var opMax = 0; +var stepMax = 0; +var lastIndex = 0; +var hasPath = false; +var hasComputedPath = false; + +var firstActiveSpan = -1; +var logStart = -1; +var logRange = 0; + +var SPAN_ID = 0; +var SPAN_X1 = SPAN_ID + 1; +var SPAN_Y1 = SPAN_X1 + 1; +var SPAN_X2 = SPAN_Y1 + 1; +var SPAN_Y2 = SPAN_X2 + 1; +var SPAN_L_T = SPAN_Y2 + 1; +var SPAN_L_TX = SPAN_L_T + 1; +var SPAN_L_TY = SPAN_L_TX + 1; +var SPAN_L_TEND = SPAN_L_TY + 1; +var SPAN_L_OTHER = SPAN_L_TEND + 1; +var SPAN_L_OTHERT = SPAN_L_OTHER + 1; +var SPAN_L_OTHERI = SPAN_L_OTHERT + 1; +var SPAN_L_SUM = SPAN_L_OTHERI + 1; +var SPAN_L_VAL = SPAN_L_SUM + 1; +var SPAN_L_OPP = SPAN_L_VAL + 1; + +var SPAN_X3 = SPAN_Y2 + 1; +var SPAN_Y3 = SPAN_X3 + 1; +var SPAN_Q_T = SPAN_Y3 + 1; +var SPAN_Q_TX = SPAN_Q_T + 1; +var SPAN_Q_TY = SPAN_Q_TX + 1; +var SPAN_Q_TEND = SPAN_Q_TY + 1; +var SPAN_Q_OTHER = SPAN_Q_TEND + 1; +var SPAN_Q_OTHERT = SPAN_Q_OTHER + 1; +var SPAN_Q_OTHERI = SPAN_Q_OTHERT + 1; +var SPAN_Q_SUM = SPAN_Q_OTHERI + 1; +var SPAN_Q_VAL = SPAN_Q_SUM + 1; +var SPAN_Q_OPP = SPAN_Q_VAL + 1; + +var SPAN_X4 = SPAN_Y3 + 1; +var SPAN_Y4 = SPAN_X4 + 1; +var SPAN_C_T = SPAN_Y4 + 1; +var SPAN_C_TX = SPAN_C_T + 1; +var SPAN_C_TY = SPAN_C_TX + 1; +var SPAN_C_TEND = SPAN_C_TY + 1; +var SPAN_C_OTHER = SPAN_C_TEND + 1; +var SPAN_C_OTHERT = SPAN_C_OTHER + 1; +var SPAN_C_OTHERI = SPAN_C_OTHERT + 1; +var SPAN_C_SUM = SPAN_C_OTHERI + 1; +var SPAN_C_VAL = SPAN_C_SUM + 1; +var SPAN_C_OPP = SPAN_C_VAL + 1; + +var ACTIVE_LINE_SPAN = 1; +var ACTIVE_QUAD_SPAN = ACTIVE_LINE_SPAN + 1; +var ACTIVE_CUBIC_SPAN = ACTIVE_QUAD_SPAN + 1; + +var ADD_MOVETO = ACTIVE_CUBIC_SPAN + 1; +var ADD_LINETO = ADD_MOVETO + 1; +var ADD_QUADTO = ADD_LINETO + 1; +var ADD_CUBICTO = ADD_QUADTO + 1; +var ADD_CLOSE = ADD_CUBICTO + 1; +var ADD_FILL = ADD_CLOSE + 1; + +var PATH_LINE = ADD_FILL + 1; +var PATH_QUAD = PATH_LINE + 1; +var PATH_CUBIC = PATH_QUAD + 1; + +var INTERSECT_LINE = PATH_CUBIC + 1; +var INTERSECT_LINE_2 = INTERSECT_LINE + 1; +var INTERSECT_LINE_NO = INTERSECT_LINE_2 + 1; +var INTERSECT_QUAD_LINE = INTERSECT_LINE_NO + 1; +var INTERSECT_QUAD_LINE_2 = INTERSECT_QUAD_LINE + 1; +var INTERSECT_QUAD_LINE_NO = INTERSECT_QUAD_LINE_2 + 1; +var INTERSECT_QUAD = INTERSECT_QUAD_LINE_NO + 1; +var INTERSECT_QUAD_2 = INTERSECT_QUAD + 1; +var INTERSECT_QUAD_NO = INTERSECT_QUAD_2 + 1; +var INTERSECT_SELF_CUBIC = INTERSECT_QUAD_NO + 1; +var INTERSECT_SELF_CUBIC_NO = INTERSECT_SELF_CUBIC + 1; +var INTERSECT_CUBIC_LINE = INTERSECT_SELF_CUBIC_NO + 1; +var INTERSECT_CUBIC_LINE_2 = INTERSECT_CUBIC_LINE + 1; +var INTERSECT_CUBIC_LINE_3 = INTERSECT_CUBIC_LINE_2 + 1; +var INTERSECT_CUBIC_LINE_NO = INTERSECT_CUBIC_LINE_3 + 1; +var INTERSECT_CUBIC_QUAD = INTERSECT_CUBIC_LINE_NO + 1; +var INTERSECT_CUBIC_QUAD_2 = INTERSECT_CUBIC_QUAD + 1; +var INTERSECT_CUBIC_QUAD_3 = INTERSECT_CUBIC_QUAD_2 + 1; +var INTERSECT_CUBIC_QUAD_4 = INTERSECT_CUBIC_QUAD_3 + 1; +var INTERSECT_CUBIC_QUAD_NO = INTERSECT_CUBIC_QUAD_4 + 1; +var INTERSECT_CUBIC = INTERSECT_CUBIC_QUAD_NO + 1; +var INTERSECT_CUBIC_2 = INTERSECT_CUBIC + 1; +var INTERSECT_CUBIC_3 = INTERSECT_CUBIC_2 + 1; +var INTERSECT_CUBIC_4 = INTERSECT_CUBIC_3 + 1; +// FIXME: add cubic 5- 9 +var INTERSECT_CUBIC_NO = INTERSECT_CUBIC_4 + 1; + +var SORT_UNARY = INTERSECT_CUBIC_NO + 1; +var SORT_BINARY = SORT_UNARY + 1; + +var OP_DIFFERENCE = SORT_BINARY + 1; +var OP_INTERSECT = OP_DIFFERENCE + 1; +var OP_UNION = OP_INTERSECT + 1; +var OP_XOR = OP_UNION + 1; + +var MARK_LINE = OP_XOR + 1; +var MARK_QUAD = MARK_LINE + 1; +var MARK_CUBIC = MARK_QUAD + 1; +var MARK_DONE_LINE = MARK_CUBIC + 1; +var MARK_DONE_QUAD = MARK_DONE_LINE + 1; +var MARK_DONE_CUBIC = MARK_DONE_QUAD + 1; +var MARK_UNSORTABLE_LINE = MARK_DONE_CUBIC + 1; +var MARK_UNSORTABLE_QUAD = MARK_UNSORTABLE_LINE + 1; +var MARK_UNSORTABLE_CUBIC = MARK_UNSORTABLE_QUAD + 1; +var MARK_SIMPLE_LINE = MARK_UNSORTABLE_CUBIC + 1; +var MARK_SIMPLE_QUAD = MARK_SIMPLE_LINE + 1; +var MARK_SIMPLE_CUBIC = MARK_SIMPLE_QUAD + 1; +var MARK_SIMPLE_DONE_LINE = MARK_SIMPLE_CUBIC + 1; +var MARK_SIMPLE_DONE_QUAD = MARK_SIMPLE_DONE_LINE + 1; +var MARK_SIMPLE_DONE_CUBIC = MARK_SIMPLE_DONE_QUAD + 1; +var MARK_DONE_UNARY_LINE = MARK_SIMPLE_DONE_CUBIC + 1; +var MARK_DONE_UNARY_QUAD = MARK_DONE_UNARY_LINE + 1; +var MARK_DONE_UNARY_CUBIC = MARK_DONE_UNARY_QUAD + 1; +var MARK_ANGLE_LAST = MARK_DONE_UNARY_CUBIC + 1; + +var COMPUTED_SET_1 = MARK_ANGLE_LAST + 1; +var COMPUTED_SET_2 = COMPUTED_SET_1 + 1; + +var ANGLE_AFTER = COMPUTED_SET_2; +var ANGLE_AFTER2 = ANGLE_AFTER + 1; + +var ACTIVE_OP = ANGLE_AFTER2 + 1; + +var FRAG_TYPE_LAST = ACTIVE_OP; + +var REC_TYPE_UNKNOWN = -1; +var REC_TYPE_PATH = 0; +var REC_TYPE_SECT = 1; +var REC_TYPE_ACTIVE = 2; +var REC_TYPE_ADD = 3; +var REC_TYPE_SORT = 4; +var REC_TYPE_OP = 5; +var REC_TYPE_MARK = 6; +var REC_TYPE_COMPUTED = 7; +var REC_TYPE_COIN = 8; +var REC_TYPE_ANGLE = 9; +var REC_TYPE_ACTIVE_OP = 10; +var REC_TYPE_LAST = REC_TYPE_ACTIVE_OP; + +function strs_to_nums(strs) { + var result = []; + for (var idx = 1; idx < strs.length; ++idx) { + var str = strs[idx]; + var num = parseFloat(str); + if (isNaN(num)) { + result.push(str); + } else { + result.push(num); + } + } + return result; +} + +function filter_str_by(id, str, regex, array) { + if (regex.test(str)) { + var strs = regex.exec(str); + var result = strs_to_nums(strs); + array.push(id); + array.push(result); + return true; + } + return false; +} + +function construct_regexp2(pattern) { + var escape = pattern.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); + escape = escape.replace(/UNSORTABLE/g, "\\*\\*\\* UNSORTABLE \\*\\*\\*"); + escape = escape.replace(/CUBIC_VAL/g, "\\(P_VAL P_VAL P_VAL P_VAL\\)"); + escape = escape.replace(/QUAD_VAL/g, "\\(P_VAL P_VAL P_VAL\\)"); + escape = escape.replace(/LINE_VAL/g, "\\(P_VAL P_VAL\\)"); + escape = escape.replace(/FILL_TYPE/g, "SkPath::k[a-zA-Z]+_FillType"); + escape = escape.replace(/PT_VAL/g, "\\(P_VAL\\)"); + escape = escape.replace(/P_VAL/g, "(-?\\d+\\.?\\d*(?:e-?\\d+)?)[Ff]?, ?(-?\\d+\\.?\\d*(?:e-?\\d+)?)[Ff]?"); + escape = escape.replace(/T_VAL/g, "(-?\\d+\\.?\\d*(?:e-?\\d+)?)"); + escape = escape.replace(/PATH/g, "pathB?"); + escape = escape.replace(/IDX/g, "(\\d+)"); + escape = escape.replace(/NUM/g, "(-?\\d+)"); + escape = escape.replace(/OPT/g, "(\\?|-?\\d+)"); + return new RegExp(escape, 'i'); +} + +function construct_regexp2c(pattern) { + var escape = pattern.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); + escape = escape.replace(/UNSORTABLE/g, "\\*\\*\\* UNSORTABLE \\*\\*\\*"); + escape = escape.replace(/CUBIC_VAL/g, "(?:\\$\\d = )?\\{\\{P_VAL\\}, \\{P_VAL\\}, \\{P_VAL\\}, \\{P_VAL\\}\\}"); + escape = escape.replace(/QUAD_VAL/g, "(?:\\$\\d = )?\\{\\{P_VAL\\}, \\{P_VAL\\}, \\{P_VAL\\}\\}"); + escape = escape.replace(/LINE_VAL/g, "(?:\\$\\d = )?\\{\\{P_VAL\\}, \\{P_VAL\\}\\}"); + escape = escape.replace(/FILL_TYPE/g, "SkPath::k[a-zA-Z]+_FillType"); + escape = escape.replace(/PT_VAL/g, "\\{\\{P_VAL\\}\\}"); + escape = escape.replace(/P_VAL/g, "(?:f?[xX] = )?(-?\\d+\\.?\\d*(?:e-?\\d+)?)[Ff]?,(?: f?[yY] = )?(-?\\d+\\.?\\d*(?:e-?\\d+)?)[Ff]?"); + escape = escape.replace(/T_VAL/g, "(-?\\d+\\.?\\d*(?:e-?\\d+)?)"); + escape = escape.replace(/OPER/g, "[a-z]+"); + escape = escape.replace(/PATH/g, "pathB?"); + escape = escape.replace(/T_F/g, "([TF])"); + escape = escape.replace(/IDX/g, "(\\d+)"); + escape = escape.replace(/NUM/g, "(-?\\d+)"); + escape = escape.replace(/OPT/g, "(\\?|-?\\d+)"); + return new RegExp(escape, 'i'); +} + +function match_regexp(str, lineNo, array, id, pattern) { + var regex = construct_regexp2(pattern); + if (filter_str_by(id, str, regex, array)) { + return true; + } + regex = construct_regexp2c(pattern); + return filter_str_by(id, str, regex, array); +} + +function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; +} + +function parse_all(test) { + var lines = test.match(/[^\r\n]+/g); + var records = []; // a rec can be the original paths, a set of intersections, a set of active spans, a sort, or a path add + var record = []; + var recType = REC_TYPE_UNKNOWN; + var lastLineNo; + var moveX, moveY; + for (var lineNo = 0; lineNo < lines.length; ++lineNo) { + var line = lines[lineNo]; + if (line.length == 0) { + continue; + } + var opStart = "SkOpSegment::"; + if (line.lastIndexOf(opStart, 0) === 0) { + line = line.substr(opStart.length); + } + var angleStart = "SkOpAngle::"; + if (line.lastIndexOf(angleStart, 0) === 0) { + line = line.substr(angleStart.length); + } + var type = line.lastIndexOf("debugShowActiveSpans", 0) === 0 ? REC_TYPE_ACTIVE + : line.lastIndexOf("debugShowTs", 0) === 0 ? REC_TYPE_COIN + : line.lastIndexOf("debugShow", 0) === 0 ? REC_TYPE_SECT + : line.lastIndexOf("activeOp", 0) === 0 ? REC_TYPE_ACTIVE_OP + : line.lastIndexOf("computed", 0) === 0 ? REC_TYPE_COMPUTED + : line.lastIndexOf("debugOne", 0) === 0 ? REC_TYPE_SORT + : line.lastIndexOf("dumpOne", 0) === 0 ? REC_TYPE_SORT + : line.lastIndexOf("pathB.", 0) === 0 ? REC_TYPE_ADD + : line.lastIndexOf("path.", 0) === 0 ? REC_TYPE_ADD + : line.lastIndexOf("after", 0) === 0 ? REC_TYPE_ANGLE + : line.lastIndexOf("mark", 0) === 0 ? REC_TYPE_MARK + : line.lastIndexOf(" {{", 0) === 0 ? REC_TYPE_COMPUTED + : line.lastIndexOf("{{", 0) === 0 ? REC_TYPE_PATH + : line.lastIndexOf("op", 0) === 0 ? REC_TYPE_OP + : line.lastIndexOf("$", 0) === 0 ? REC_TYPE_PATH + : REC_TYPE_UNKNOWN; + if (recType != type || recType == REC_TYPE_ADD || recType == REC_TYPE_SECT + || recType == REC_TYPE_ACTIVE_OP || recType == REC_TYPE_ANGLE) { + if (recType != REC_TYPE_UNKNOWN) { + records.push(recType); + records.push(lastLineNo); + records.push(record); + } + record = []; + recType = type; + lastLineNo = lineNo; + } + var found = false; + switch (recType) { + case REC_TYPE_ACTIVE: + found = match_regexp(line, lineNo, record, ACTIVE_LINE_SPAN, "debugShowActiveSpans" + +" id=IDX LINE_VAL t=T_VAL PT_VAL tEnd=T_VAL other=IDX otherT=T_VAL otherIndex=IDX windSum=OPT windValue=IDX oppValue=NUM" + ) || match_regexp(line, lineNo, record, ACTIVE_QUAD_SPAN, "debugShowActiveSpans" + +" id=IDX QUAD_VAL t=T_VAL PT_VAL tEnd=T_VAL other=IDX otherT=T_VAL otherIndex=IDX windSum=OPT windValue=IDX oppValue=NUM" + ) || match_regexp(line, lineNo, record, ACTIVE_CUBIC_SPAN, "debugShowActiveSpans" + +" id=IDX CUBIC_VAL t=T_VAL PT_VAL tEnd=T_VAL other=IDX otherT=T_VAL otherIndex=IDX windSum=OPT windValue=IDX oppValue=NUM" + ); + break; + case REC_TYPE_ACTIVE_OP: + found = match_regexp(line, lineNo, record, ACTIVE_OP, "activeOp" + +" id=IDX t=T_VAL tEnd=T_VAL op=OPER miFrom=NUM miTo=NUM suFrom=NUM suTo=NUM result=IDX" + ); + break; + case REC_TYPE_ADD: + if (match_regexp(line, lineNo, record, ADD_MOVETO, "PATH.moveTo(P_VAL);")) { + moveX = record[1][0]; + moveY = record[1][1]; + found = true; + } else if (match_regexp(line, lineNo, record, ADD_LINETO, "PATH.lineTo(P_VAL);")) { + record[1].unshift(moveY); + record[1].unshift(moveX); + moveX = record[1][2]; + moveY = record[1][3]; + found = true; + } else if (match_regexp(line, lineNo, record, ADD_QUADTO, "PATH.quadTo(P_VAL, P_VAL);")) { + record[1].unshift(moveY); + record[1].unshift(moveX); + moveX = record[1][4]; + moveY = record[1][5]; + found = true; + } else if (match_regexp(line, lineNo, record, ADD_CUBICTO, "PATH.cubicTo(P_VAL, P_VAL, P_VAL);")) { + record[1].unshift(moveY); + record[1].unshift(moveX); + moveX = record[1][6]; + moveY = record[1][7]; + found = true; + } else if (match_regexp(line, lineNo, record, ADD_FILL, "PATH.setFillType(FILL_TYPE);")) { + found = true; + } else { + found = match_regexp(line, lineNo, record, ADD_CLOSE, "PATH.close();"); + } + break; + case REC_TYPE_ANGLE: + found = match_regexp(line, lineNo, record, ANGLE_AFTER, "after " + +"id=IDX IDX/IDX tStart=T_VAL tEnd=T_VAL < id=IDX IDX/IDX tStart=T_VAL tEnd=T_VAL < id=IDX IDX/IDX tStart=T_VAL tEnd=T_VAL T_F IDX"); + if (found) { + break; + } + found = match_regexp(line, lineNo, record, ANGLE_AFTER2, "after " + +"[IDX/IDX] NUM/NUM tStart=T_VAL tEnd=T_VAL < [IDX/IDX] NUM/NUM tStart=T_VAL tEnd=T_VAL < [IDX/IDX] NUM/NUM tStart=T_VAL tEnd=T_VAL T_F IDX"); + break; + case REC_TYPE_COIN: + found = true; + break; + case REC_TYPE_COMPUTED: + found = line == "computed quadratics given" + || match_regexp(line, lineNo, record, COMPUTED_SET_1, "computed quadratics set 1" + ) || match_regexp(line, lineNo, record, COMPUTED_SET_2, "computed quadratics set 2" + ) || match_regexp(line, lineNo, record, PATH_QUAD, " QUAD_VAL," + ) || match_regexp(line, lineNo, record, PATH_CUBIC, " CUBIC_VAL," + ); + break; + case REC_TYPE_PATH: + found = match_regexp(line, lineNo, record, PATH_LINE, "LINE_VAL" + ) || match_regexp(line, lineNo, record, PATH_QUAD, "QUAD_VAL" + ) || match_regexp(line, lineNo, record, PATH_CUBIC, "CUBIC_VAL" + ); + break; + case REC_TYPE_SECT: + found = match_regexp(line, lineNo, record, INTERSECT_LINE, "debugShowLineIntersection" + +" wtTs[0]=T_VAL LINE_VAL PT_VAL wnTs[0]=T_VAL LINE_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_LINE_2, "debugShowLineIntersection" + +" wtTs[0]=T_VAL LINE_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wnTs[0]=T_VAL LINE_VAL wnTs[1]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_LINE_NO, "debugShowLineIntersection" + +" no intersect LINE_VAL LINE_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_QUAD_LINE, "debugShowQuadLineIntersection" + +" wtTs[0]=T_VAL QUAD_VAL PT_VAL wnTs[0]=T_VAL LINE_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_QUAD_LINE_2, "debugShowQuadLineIntersection" + +" wtTs[0]=T_VAL QUAD_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wnTs[0]=T_VAL LINE_VAL wnTs[1]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_QUAD_LINE_NO, "debugShowQuadLineIntersection" + +" no intersect QUAD_VAL LINE_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_QUAD, "debugShowQuadIntersection" + +" wtTs[0]=T_VAL QUAD_VAL PT_VAL wnTs[0]=T_VAL QUAD_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_QUAD_2, "debugShowQuadIntersection" + +" wtTs[0]=T_VAL QUAD_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wnTs[0]=T_VAL QUAD_VAL wnTs[1]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_QUAD_NO, "debugShowQuadIntersection" + +" no intersect QUAD_VAL QUAD_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_LINE, "debugShowCubicLineIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wnTs[0]=T_VAL LINE_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_LINE_2, "debugShowCubicLineIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wnTs[0]=T_VAL LINE_VAL wnTs[1]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_LINE_3, "debugShowCubicLineIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wtTs[2]=T_VAL PT_VAL wnTs[0]=T_VAL LINE_VAL wnTs[1]=T_VAL wnTs[2]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_LINE_NO, "debugShowCubicLineIntersection" + +" no intersect CUBIC_VAL LINE_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_QUAD, "debugShowCubicQuadIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wnTs[0]=T_VAL QUAD_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_QUAD_2, "debugShowCubicQuadIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wnTs[0]=T_VAL QUAD_VAL wnTs[1]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_QUAD_3, "debugShowCubicQuadIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wtTs[2]=T_VAL PT_VAL wnTs[0]=T_VAL QUAD_VAL wnTs[1]=T_VAL wnTs[2]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_QUAD_4, "debugShowCubicQuadIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wtTs[2]=T_VAL wtTs[3]=T_VAL PT_VAL wnTs[0]=T_VAL QUAD_VAL wnTs[1]=T_VAL wnTs[2]=T_VAL wnTs[3]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_QUAD_NO, "debugShowCubicQuadIntersection" + +" no intersect CUBIC_VAL QUAD_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC, "debugShowCubicIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wnTs[0]=T_VAL CUBIC_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_2, "debugShowCubicIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wnTs[0]=T_VAL CUBIC_VAL wnTs[1]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_3, "debugShowCubicIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wtTs[2]=T_VAL PT_VAL wnTs[0]=T_VAL CUBIC_VAL wnTs[1]=T_VAL wnTs[2]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_4, "debugShowCubicIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wtTs[2]=T_VAL PT_VAL wtTs[3]=T_VAL PT_VAL wnTs[0]=T_VAL CUBIC_VAL wnTs[1]=T_VAL wnTs[2]=T_VAL wnTs[3]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_NO, "debugShowCubicIntersection" + +" no intersect CUBIC_VAL CUBIC_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_SELF_CUBIC, "debugShowCubicIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_SELF_CUBIC_NO, "debugShowCubicIntersection" + +" no self intersect CUBIC_VAL" + ); + break; + case REC_TYPE_SORT: + var hasDone = / done/.test(line); + var hasUnorderable = / unorderable/.test(line); + var hasSmall = / small/.test(line); + var hasTiny = / tiny/.test(line); + var hasOperand = / operand/.test(line); + var hasStop = / stop/.test(line); + line.replace(/[ a-z]+$/, ""); + found = match_regexp(line, lineNo, record, SORT_UNARY, "debugOne" + +" [IDX/IDX] next=IDX/IDX sect=IDX/IDX s=T_VAL [IDX] e=T_VAL [IDX] sgn=NUM windVal=IDX windSum=OPT" + ) || match_regexp(line, lineNo, record, SORT_BINARY, "debugOne" + +" [IDX/IDX] next=IDX/IDX sect=IDX/IDX s=T_VAL [IDX] e=T_VAL [IDX] sgn=NUM windVal=IDX windSum=OPT oppVal=IDX oppSum=OPT" + ) || match_regexp(line, lineNo, record, SORT_UNARY, "dumpOne" + +" [IDX/IDX] next=IDX/IDX sect=NUM/NUM s=T_VAL [IDX] e=T_VAL [IDX] sgn=NUM windVal=IDX windSum=OPT" + ) || match_regexp(line, lineNo, record, SORT_BINARY, "dumpOne" + +" [IDX/IDX] next=IDX/IDX sect=NUM/NUM s=T_VAL [IDX] e=T_VAL [IDX] sgn=NUM windVal=IDX windSum=OPT oppVal=IDX oppSum=OPT" + ); + if (found) { + record[1].push(hasDone); + record[1].push(hasUnorderable); + record[1].push(hasSmall); + record[1].push(hasTiny); + record[1].push(hasOperand); + record[1].push(hasStop); + } + break; + case REC_TYPE_MARK: + found = match_regexp(line, lineNo, record, MARK_LINE, "markWinding" + +" id=IDX LINE_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM newOppSum=NUM oppSum=OPT windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_QUAD, "markWinding" + +" id=IDX QUAD_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM newOppSum=NUM oppSum=OPT windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_CUBIC, "markWinding" + +" id=IDX CUBIC_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM newOppSum=NUM oppSum=OPT windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_DONE_LINE, "markDoneBinary" + +" id=IDX LINE_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM newOppSum=NUM oppSum=OPT windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_DONE_QUAD, "markDoneBinary" + +" id=IDX QUAD_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM newOppSum=NUM oppSum=OPT windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_DONE_CUBIC, "markDoneBinary" + +" id=IDX CUBIC_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM newOppSum=NUM oppSum=OPT windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_UNSORTABLE_LINE, "markUnsortable" + +" id=IDX LINE_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_UNSORTABLE_QUAD, "markUnsortable" + +" id=IDX QUAD_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_UNSORTABLE_CUBIC, "markUnsortable" + +" id=IDX CUBIC_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_SIMPLE_LINE, "markWinding" + +" id=IDX LINE_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_SIMPLE_QUAD, "markWinding" + +" id=IDX QUAD_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_SIMPLE_CUBIC, "markWinding" + +" id=IDX CUBIC_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_SIMPLE_DONE_LINE, "markDone" + +" id=IDX LINE_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_SIMPLE_DONE_QUAD, "markDone" + +" id=IDX QUAD_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_SIMPLE_DONE_CUBIC, "markDone" + +" id=IDX CUBIC_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_DONE_UNARY_LINE, "markDoneUnary" + +" id=IDX LINE_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_DONE_UNARY_QUAD, "markDoneUnary" + +" id=IDX QUAD_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_DONE_UNARY_CUBIC, "markDoneUnary" + +" id=IDX CUBIC_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_ANGLE_LAST, "markAngle" + +" last id=IDX windSum=OPT small=IDX"); + break; + case REC_TYPE_OP: + if (line.lastIndexOf("oppSign oppSign=", 0) === 0 + || line.lastIndexOf("operator<", 0) === 0) { + found = true; + break; + } + found = match_regexp(line, lineNo, record, OP_DIFFERENCE, "op difference" + ) || match_regexp(line, lineNo, record, OP_INTERSECT, "op intersect" + ) || match_regexp(line, lineNo, record, OP_UNION, "op union" + ) || match_regexp(line, lineNo, record, OP_XOR, "op xor" + ); + break; + case REC_TYPE_UNKNOWN: + found = true; + break; + } + if (!found) { + console.log(line + " [" + lineNo + "] of type " + type + " not found"); + } + } + if (recType != REC_TYPE_UNKNOWN) { + records.push(recType); + records.push(lastLineNo); + records.push(record); + } + if (records.length >= 1) { + tests[testIndex] = records; + testLines[testIndex] = lines; + } +} + +function init(test) { + var canvas = document.getElementById('canvas'); + if (!canvas.getContext) return; + ctx = canvas.getContext('2d'); + var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1; + var unscaledWidth = window.innerWidth - 20; + var unscaledHeight = window.innerHeight - 20; + screenWidth = unscaledWidth; + screenHeight = unscaledHeight; + canvas.width = unscaledWidth * resScale; + canvas.height = unscaledHeight * resScale; + canvas.style.width = unscaledWidth + 'px'; + canvas.style.height = unscaledHeight + 'px'; + if (resScale != 1) { + ctx.scale(resScale, resScale); + } + xmin = Infinity; + xmax = -Infinity; + ymin = Infinity; + ymax = -Infinity; + hasPath = hasComputedPath = false; + firstActiveSpan = -1; + for (var tIndex = 0; tIndex < test.length; tIndex += 3) { + var recType = test[tIndex]; + if (!typeof recType == 'number' || recType < REC_TYPE_UNKNOWN || recType > REC_TYPE_LAST) { + console.log("unknown rec type: " + recType); + throw "stop execution"; + } + var records = test[tIndex + 2]; + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + if (!typeof fragType == 'number' || fragType < 1 || fragType > FRAG_TYPE_LAST) { + console.log("unknown in range frag type: " + fragType); + throw "stop execution"; + } + var frags = records[recordIndex + 1]; + var first = 0; + var last = -1; + var first2 = 0; + var last2 = 0; + switch (recType) { + case REC_TYPE_COMPUTED: + if (fragType == COMPUTED_SET_1 || fragType == COMPUTED_SET_2) { + break; + } + hasComputedPath = true; + case REC_TYPE_PATH: + switch (fragType) { + case PATH_LINE: + last = 4; + break; + case PATH_QUAD: + last = 6; + break; + case PATH_CUBIC: + last = 8; + break; + default: + console.log("unknown " + (recType == REC_TYPE_PATH ? "REC_TYPE_PATH" + : "REC_TYPE_COMPUTED") + " frag type:" + fragType); + throw "stop execution"; + } + if (recType == REC_TYPE_PATH) { + hasPath = true; + } + break; + case REC_TYPE_ACTIVE: + if (firstActiveSpan < 0) { + firstActiveSpan = tIndex; + } + first = 1; + switch (fragType) { + case ACTIVE_LINE_SPAN: + last = 5; + break; + case ACTIVE_QUAD_SPAN: + last = 7; + break; + case ACTIVE_CUBIC_SPAN: + last = 9; + break; + default: + console.log("unknown REC_TYPE_ACTIVE frag type: " + fragType); + throw "stop execution"; + } + break; + case REC_TYPE_ADD: + switch (fragType) { + case ADD_MOVETO: + break; + case ADD_LINETO: + last = 4; + break; + case ADD_QUADTO: + last = 6; + break; + case ADD_CUBICTO: + last = 8; + break; + case ADD_CLOSE: + case ADD_FILL: + break; + default: + console.log("unknown REC_TYPE_ADD frag type: " + fragType); + throw "stop execution"; + } + break; + case REC_TYPE_SECT: + switch (fragType) { + case INTERSECT_LINE: + first = 1; last = 5; first2 = 8; last2 = 12; + break; + case INTERSECT_LINE_2: + first = 1; last = 5; first2 = 11; last2 = 15; + break; + case INTERSECT_LINE_NO: + first = 0; last = 4; first2 = 4; last2 = 8; + break; + case INTERSECT_QUAD_LINE: + first = 1; last = 7; first2 = 10; last2 = 14; + break; + case INTERSECT_QUAD_LINE_2: + first = 1; last = 7; first2 = 13; last2 = 17; + break; + case INTERSECT_QUAD_LINE_NO: + first = 0; last = 6; first2 = 6; last2 = 10; + break; + case INTERSECT_QUAD: + first = 1; last = 7; first2 = 10; last2 = 16; + break; + case INTERSECT_QUAD_2: + first = 1; last = 7; first2 = 13; last2 = 19; + break; + case INTERSECT_QUAD_NO: + first = 0; last = 6; first2 = 6; last2 = 12; + break; + case INTERSECT_SELF_CUBIC: + first = 1; last = 9; + break; + case INTERSECT_SELF_CUBIC_NO: + first = 0; last = 8; + break; + case INTERSECT_CUBIC_LINE: + first = 1; last = 9; first2 = 12; last2 = 16; + break; + case INTERSECT_CUBIC_LINE_2: + first = 1; last = 9; first2 = 15; last2 = 19; + break; + case INTERSECT_CUBIC_LINE_3: + first = 1; last = 9; first2 = 18; last2 = 22; + break; + case INTERSECT_CUBIC_LINE_NO: + first = 0; last = 8; first2 = 8; last2 = 12; + break; + case INTERSECT_CUBIC_QUAD: + first = 1; last = 9; first2 = 12; last2 = 18; + break; + case INTERSECT_CUBIC_QUAD_2: + first = 1; last = 9; first2 = 15; last2 = 21; + break; + case INTERSECT_CUBIC_QUAD_3: + first = 1; last = 9; first2 = 18; last2 = 24; + break; + case INTERSECT_CUBIC_QUAD_4: + first = 1; last = 9; first2 = 21; last2 = 27; + break; + case INTERSECT_CUBIC_QUAD_NO: + first = 0; last = 8; first2 = 8; last2 = 14; + break; + case INTERSECT_CUBIC: + first = 1; last = 9; first2 = 12; last2 = 20; + break; + case INTERSECT_CUBIC_2: + first = 1; last = 9; first2 = 15; last2 = 23; + break; + case INTERSECT_CUBIC_3: + first = 1; last = 9; first2 = 18; last2 = 26; + break; + case INTERSECT_CUBIC_4: + first = 1; last = 9; first2 = 21; last2 = 29; + break; + case INTERSECT_CUBIC_NO: + first = 0; last = 8; first2 = 8; last2 = 16; + break; + default: + console.log("unknown REC_TYPE_SECT frag type: " + fragType); + throw "stop execution"; + } + break; + default: + continue; + } + for (var idx = first; idx < last; idx += 2) { + xmin = Math.min(xmin, frags[idx]); + xmax = Math.max(xmax, frags[idx]); + ymin = Math.min(ymin, frags[idx + 1]); + ymax = Math.max(ymax, frags[idx + 1]); + } + for (var idx = first2; idx < last2; idx += 2) { + xmin = Math.min(xmin, frags[idx]); + xmax = Math.max(xmax, frags[idx]); + ymin = Math.min(ymin, frags[idx + 1]); + ymax = Math.max(ymax, frags[idx + 1]); + } + } + } + var angleBounds = [Infinity, Infinity, -Infinity, -Infinity]; + for (var tIndex = 0; tIndex < test.length; tIndex += 3) { + var recType = test[tIndex]; + var records = test[tIndex + 2]; + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + var frags = records[recordIndex + 1]; + switch (recType) { + case REC_TYPE_ACTIVE_OP: + if (!draw_op) { + break; + } + { + var curve = curvePartialByID(test, frags[0], frags[1], frags[2]); + curve_extremes(curve, angleBounds); + } + break; + case REC_TYPE_ANGLE: + if (!draw_angle) { + break; + } + if (fragType == ANGLE_AFTER) { + var curve = curvePartialByID(test, frags[0], frags[3], frags[4]); + curve_extremes(curve, angleBounds); + curve = curvePartialByID(test, frags[5], frags[8], frags[9]); + curve_extremes(curve, angleBounds); + curve = curvePartialByID(test, frags[10], frags[13], frags[14]); + } else if (fragType == ANGLE_AFTER2) { + var curve = curvePartialByID(test, frags[0], frags[4], frags[5]); + curve_extremes(curve, angleBounds); + curve = curvePartialByID(test, frags[6], frags[10], frags[11]); + curve_extremes(curve, angleBounds); + curve = curvePartialByID(test, frags[12], frags[16], frags[17]); + } + break; + case REC_TYPE_SORT: + if (!draw_sort) { + break; + } + if (fragType == SORT_UNARY || fragType == SORT_BINARY) { + var curve = curvePartialByID(test, frags[0], frags[6], frags[8]); + curve_extremes(curve, angleBounds); + } + break; + } + } + } + xmin = Math.min(xmin, angleBounds[0]); + ymin = Math.min(ymin, angleBounds[1]); + xmax = Math.max(xmax, angleBounds[2]); + ymax = Math.max(ymax, angleBounds[3]); + setScale(xmin, xmax, ymin, ymax); + if (hasPath == false && hasComputedPath == true && !draw_computed) { + draw_computed = 3; // show both quadratics and cubics + } + if (hasPath == true && hasComputedPath == false && draw_computed) { + draw_computed = 0; + } +} + +function curveByID(test, id) { + var tIndex = firstActiveSpan; + if (tIndex < 0) { + return []; + } + while (tIndex < test.length) { + var recType = test[tIndex]; + if (recType != REC_TYPE_ACTIVE) { + return []; + } + var records = test[tIndex + 2]; + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + var frags = records[recordIndex + 1]; + if (frags[0] == id) { + switch (fragType) { + case ACTIVE_LINE_SPAN: + return [frags[1], frags[2], frags[3], frags[4]]; + case ACTIVE_QUAD_SPAN: + return [frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6]]; + case ACTIVE_CUBIC_SPAN: + return [frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6], frags[7], frags[8]]; + } + } + } + tIndex += 3; + } + return []; +} + +function curvePartialByID(test, id, t0, t1) { + var tIndex = firstActiveSpan; + if (tIndex < 0) { + return []; + } + while (tIndex < test.length) { + var recType = test[tIndex]; + if (recType != REC_TYPE_ACTIVE) { + return []; + } + var records = test[tIndex + 2]; + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + var frags = records[recordIndex + 1]; + if (frags[0] == id) { + switch (fragType) { + case ACTIVE_LINE_SPAN: + return linePartial(frags[1], frags[2], frags[3], frags[4], t0, t1); + case ACTIVE_QUAD_SPAN: + return quadPartial(frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6], t0, t1); + case ACTIVE_CUBIC_SPAN: + return cubicPartial(frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6], frags[7], frags[8], t0, t1); + } + } + } + tIndex += 3; + } + return []; +} + +function idByCurve(test, frag, type) { + var tIndex = firstActiveSpan; + if (tIndex < 0) { + return -1; + } + while (tIndex < test.length) { + var recType = test[tIndex]; + if (recType != REC_TYPE_ACTIVE) { + return -1; + } + var records = test[tIndex + 2]; + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + var frags = records[recordIndex + 1]; + switch (fragType) { + case ACTIVE_LINE_SPAN: + if (type != PATH_LINE) { + continue; + } + if (frag[0] != frags[1] || frag[1] != frags[2] + || frag[2] != frags[3] || frag[3] != frags[4]) { + continue; + } + return frags[0]; + case ACTIVE_QUAD_SPAN: + if (type != PATH_QUAD) { + continue; + } + if (frag[0] != frags[1] || frag[1] != frags[2] + || frag[2] != frags[3] || frag[3] != frags[4] + || frag[4] != frags[5] || frag[5] != frags[6]) { + continue; + } + return frags[0]; + case ACTIVE_CUBIC_SPAN: + if (type != PATH_CUBIC) { + continue; + } + if (frag[0] != frags[1] || frag[1] != frags[2] + || frag[2] != frags[3] || frag[3] != frags[4] + || frag[4] != frags[5] || frag[5] != frags[6] + || frag[6] != frags[7] || frag[7] != frags[8]) { + continue; + } + return frags[0]; + } + } + ++tIndex; + } + return -1; +} + +function curve_extremes(curve, bounds) { + for (var index = 0; index < curve.length; index += 2) { + var x = curve[index]; + var y = curve[index + 1]; + bounds[0] = Math.min(bounds[0], x); + bounds[1] = Math.min(bounds[1], y); + bounds[2] = Math.max(bounds[2], x); + bounds[3] = Math.max(bounds[3], y); + } +} + +function setScale(x0, x1, y0, y1) { + var srcWidth = x1 - x0; + var srcHeight = y1 - y0; + var usableWidth = screenWidth; + var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10)); + var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10)); + usableWidth -= (xDigits + yDigits) * 10; + usableWidth -= decimal_places * 10; + if (draw_legend) { + usableWidth -= 40; + } + var hscale = usableWidth / srcWidth; + var vscale = screenHeight / srcHeight; + scale = Math.min(hscale, vscale); + var invScale = 1 / scale; + var sxmin = x0 - invScale * 5; + var symin = y0 - invScale * 10; + var sxmax = x1 + invScale * (6 * decimal_places + 10); + var symax = y1 + invScale * 10; + srcWidth = sxmax - sxmin; + srcHeight = symax - symin; + hscale = usableWidth / srcWidth; + vscale = screenHeight / srcHeight; + scale = Math.min(hscale, vscale); + srcLeft = sxmin; + srcTop = symin; +} + +function drawArc(curve, op, from, to) { + var type = PATH_LINE + (curve.length / 2 - 2); + var pt = pointAtT(curve, type, op ? 0.4 : 0.6); + var dy = pt.y - curve[1]; + var dx = pt.x - curve[0]; + var dist = Math.sqrt(dy * dy + dx * dx); + var _dist = dist * scale; + var angle = Math.atan2(dy, dx); + var _px = (curve[0] - srcLeft) * scale; + var _py = (curve[1] - srcTop) * scale; + var divisor = 4; + var endDist; + do { + var ends = []; + for (var index = -1; index <= 1; index += 2) { + var px = Math.cos(index * Math.PI / divisor); + var py = Math.sin(index * Math.PI / divisor); + ends.push(px); + ends.push(py); + } + var endDx = (ends[2] - ends[0]) * scale * dist; + var endDy = (ends[3] - ends[1]) * scale * dist; + endDist = Math.sqrt(endDx * endDx + endDy * endDy); + if (endDist < 100) { + break; + } + divisor *= 2; + } while (true); + if (endDist < 30) { + return; + } + if (op) { + divisor *= 2; + } + ctx.strokeStyle = op ? "rgba(210,0,45, 0.4)" : "rgba(90,90,90, 0.5)"; + ctx.beginPath(); + ctx.arc(_px, _py, _dist, angle - Math.PI / divisor, angle + Math.PI / divisor, false); + ctx.stroke(); + var saveAlign = ctx.textAlign; + var saveStyle = ctx.fillStyle; + var saveFont = ctx.font; + ctx.textAlign = "center"; + ctx.fillStyle = "black"; + ctx.font = "normal 24px Arial"; + divisor *= 0.8; + for (var index = -1; index <= 1; index += 2) { + var px = curve[0] + Math.cos(angle + index * Math.PI / divisor) * dist; + var py = curve[1] + Math.sin(angle + index * Math.PI / divisor) * dist; + var _px = (px - srcLeft) * scale; + var _py = (py - srcTop) * scale; + ctx.fillText(index < 0 ? to.toString() : from.toString(), _px, _py + 8); + } + ctx.textAlign = saveAlign; + ctx.fillStyle = saveStyle; + ctx.font = saveFont; +} + +function drawPoint(px, py, end) { + for (var pts = 0; pts < drawnPts.length; pts += 2) { + var x = drawnPts[pts]; + var y = drawnPts[pts + 1]; + if (px == x && py == y) { + return; + } + } + drawnPts.push(px); + drawnPts.push(py); + var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places); + var _px = (px - srcLeft) * scale; + var _py = (py - srcTop) * scale; + ctx.beginPath(); + ctx.arc(_px, _py, 3, 0, Math.PI*2, true); + ctx.closePath(); + if (end) { + ctx.fill(); + } else { + ctx.stroke(); + } + if (debug_xy) { + ctx.textAlign = "left"; + ctx.fillText(label, _px + 5, _py); + } +} + +function drawPoints(ptArray, curveType, drawControls) { + var count = (curveType - PATH_LINE + 2) * 2; + for (var idx = 0; idx < count; idx += 2) { + if (!drawControls && idx != 0 && idx != count - 2) { + continue; + } + drawPoint(ptArray[idx], ptArray[idx + 1], idx == 0 || idx == count - 2); + } +} + +function drawControlLines(curve, curveType, drawEnd) { + if (curveType == PATH_LINE) { + return; + } + ctx.strokeStyle = "rgba(0,0,0, 0.3)"; + drawLine(curve[0], curve[1], curve[2], curve[3]); + drawLine(curve[2], curve[3], curve[4], curve[5]); + if (curveType == PATH_CUBIC) { + drawLine(curve[4], curve[5], curve[6], curve[7]); + if (drawEnd > 1) { + drawLine(curve[6], curve[7], curve[0], curve[1]); + if (drawEnd > 2) { + drawLine(curve[0], curve[1], curve[4], curve[5]); + drawLine(curve[6], curve[7], curve[2], curve[3]); + } + } + } else if (drawEnd > 1) { + drawLine(curve[4], curve[5], curve[0], curve[1]); + } +} + +function pointAtT(curve, curveType, t) { + var xy = {}; + switch (curveType) { + case PATH_LINE: + var a = 1 - t; + var b = t; + xy.x = a * curve[0] + b * curve[2]; + xy.y = a * curve[1] + b * curve[3]; + break; + case PATH_QUAD: + var one_t = 1 - t; + var a = one_t * one_t; + var b = 2 * one_t * t; + var c = t * t; + xy.x = a * curve[0] + b * curve[2] + c * curve[4]; + xy.y = a * curve[1] + b * curve[3] + c * curve[5]; + break; + case PATH_CUBIC: + var one_t = 1 - t; + var one_t2 = one_t * one_t; + var a = one_t2 * one_t; + var b = 3 * one_t2 * t; + var t2 = t * t; + var c = 3 * one_t * t2; + var d = t2 * t; + xy.x = a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6]; + xy.y = a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7]; + break; + } + return xy; +} + +function drawPointAtT(curve, curveType) { + var x, y; + var xy = pointAtT(curve, curveType, curveT); + drawPoint(xy.x, xy.y, true); + if (!draw_intersectT) { + return; + } + ctx.fillStyle = "red"; + drawTAtPointUp(xy.x, xy.y, curveT); +} + +function drawTAtPointUp(px, py, t) { + var label = t.toFixed(decimal_places); + var _px = (px - srcLeft)* scale; + var _py = (py - srcTop) * scale; + ctx.fillText(label, _px + 5, _py - 10); +} + +function drawTAtPointDown(px, py, t) { + var label = t.toFixed(decimal_places); + var _px = (px - srcLeft)* scale; + var _py = (py - srcTop) * scale; + ctx.fillText(label, _px + 5, _py + 10); +} + +function alreadyDrawnLine(x1, y1, x2, y2) { + if (collect_bounds) { + if (focus_enabled) { + focusXmin = Math.min(focusXmin, x1, x2); + focusYmin = Math.min(focusYmin, y1, y2); + focusXmax = Math.max(focusXmax, x1, x2); + focusYmax = Math.max(focusYmax, y1, y2); + } + return true; + } + for (var pts = 0; pts < drawnLines.length; pts += 4) { + if (x1 == drawnLines[pts] && y1 == drawnLines[pts + 1] + && x2 == drawnLines[pts + 2] && y2 == drawnLines[pts + 3]) { + return true; + } + } + drawnLines.push(x1); + drawnLines.push(y1); + drawnLines.push(x2); + drawnLines.push(y2); + return false; +} + +function drawLine(x1, y1, x2, y2) { + if (alreadyDrawnLine(x1, y1, x2, y2)) { + return; + } + ctx.beginPath(); + ctx.moveTo((x1 - srcLeft) * scale, + (y1 - srcTop) * scale); + ctx.lineTo((x2 - srcLeft) * scale, + (y2 - srcTop) * scale); + ctx.stroke(); +} + +function linePartial(x1, y1, x2, y2, t1, t2) { + var dx = x1 - x2; + var dy = y1 - y2; + var array = [ + x1 - t1 * dx, + y1 - t1 * dy, + x1 - t2 * dx, + y1 - t2 * dy + ]; + return array; +} + +function drawLinePartial(x1, y1, x2, y2, t1, t2) { + var a = linePartial(x1, y1, x2, y2, t1, t2); + var ax = a[0]; + var ay = a[1]; + var bx = a[2]; + var by = a[3]; + if (alreadyDrawnLine(ax, ay, bx, by)) { + return; + } + ctx.beginPath(); + ctx.moveTo((ax - srcLeft) * scale, + (ay - srcTop) * scale); + ctx.lineTo((bx - srcLeft) * scale, + (by - srcTop) * scale); + ctx.stroke(); +} + +function alreadyDrawnQuad(x1, y1, x2, y2, x3, y3) { + if (collect_bounds) { + if (focus_enabled) { + focusXmin = Math.min(focusXmin, x1, x2, x3); + focusYmin = Math.min(focusYmin, y1, y2, y3); + focusXmax = Math.max(focusXmax, x1, x2, x3); + focusYmax = Math.max(focusYmax, y1, y2, y3); + } + return true; + } + for (var pts = 0; pts < drawnQuads.length; pts += 6) { + if (x1 == drawnQuads[pts] && y1 == drawnQuads[pts + 1] + && x2 == drawnQuads[pts + 2] && y2 == drawnQuads[pts + 3] + && x3 == drawnQuads[pts + 4] && y3 == drawnQuads[pts + 5]) { + return true; + } + } + drawnQuads.push(x1); + drawnQuads.push(y1); + drawnQuads.push(x2); + drawnQuads.push(y2); + drawnQuads.push(x3); + drawnQuads.push(y3); + return false; +} + +function drawQuad(x1, y1, x2, y2, x3, y3) { + if (alreadyDrawnQuad(x1, y1, x2, y2, x3, y3)) { + return; + } + ctx.beginPath(); + ctx.moveTo((x1 - srcLeft) * scale, + (y1 - srcTop) * scale); + ctx.quadraticCurveTo((x2 - srcLeft) * scale, + (y2 - srcTop) * scale, + (x3 - srcLeft) * scale, + (y3 - srcTop) * scale); + ctx.stroke(); +} + +function interp(A, B, t) { + return A + (B - A) * t; +} + +function interp_quad_coords(x1, x2, x3, t) +{ + var ab = interp(x1, x2, t); + var bc = interp(x2, x3, t); + var abc = interp(ab, bc, t); + return abc; +} + +function quadPartial(x1, y1, x2, y2, x3, y3, t1, t2) { + var ax = interp_quad_coords(x1, x2, x3, t1); + var ay = interp_quad_coords(y1, y2, y3, t1); + var dx = interp_quad_coords(x1, x2, x3, (t1 + t2) / 2); + var dy = interp_quad_coords(y1, y2, y3, (t1 + t2) / 2); + var cx = interp_quad_coords(x1, x2, x3, t2); + var cy = interp_quad_coords(y1, y2, y3, t2); + var bx = 2*dx - (ax + cx)/2; + var by = 2*dy - (ay + cy)/2; + var array = [ + ax, ay, bx, by, cx, cy + ]; + return array; +} + +function drawQuadPartial(x1, y1, x2, y2, x3, y3, t1, t2) { + var a = quadPartial(x1, y1, x2, y2, x3, y3, t1, t2); + var ax = a[0]; + var ay = a[1]; + var bx = a[2]; + var by = a[3]; + var cx = a[4]; + var cy = a[5]; + if (alreadyDrawnQuad(ax, ay, bx, by, cx, cy)) { + return; + } + ctx.beginPath(); + ctx.moveTo((ax - srcLeft) * scale, + (ay - srcTop) * scale); + ctx.quadraticCurveTo((bx - srcLeft) * scale, + (by - srcTop) * scale, + (cx - srcLeft) * scale, + (cy - srcTop) * scale); + ctx.stroke(); +} + +function alreadyDrawnCubic(x1, y1, x2, y2, x3, y3, x4, y4) { + if (collect_bounds) { + if (focus_enabled) { + focusXmin = Math.min(focusXmin, x1, x2, x3, x4); + focusYmin = Math.min(focusYmin, y1, y2, y3, y4); + focusXmax = Math.max(focusXmax, x1, x2, x3, x4); + focusYmax = Math.max(focusYmax, y1, y2, y3, y4); + } + return true; + } + for (var pts = 0; pts < drawnCubics.length; pts += 8) { + if (x1 == drawnCubics[pts] && y1 == drawnCubics[pts + 1] + && x2 == drawnCubics[pts + 2] && y2 == drawnCubics[pts + 3] + && x3 == drawnCubics[pts + 4] && y3 == drawnCubics[pts + 5] + && x4 == drawnCubics[pts + 6] && y4 == drawnCubics[pts + 7]) { + return true; + } + } + drawnCubics.push(x1); + drawnCubics.push(y1); + drawnCubics.push(x2); + drawnCubics.push(y2); + drawnCubics.push(x3); + drawnCubics.push(y3); + drawnCubics.push(x4); + drawnCubics.push(y4); + return false; +} + +function drawCubic(x1, y1, x2, y2, x3, y3, x4, y4) { + if (alreadyDrawnCubic(x1, y1, x2, y2, x3, y3, x4, y4)) { + return; + } + ctx.beginPath(); + ctx.moveTo((x1 - srcLeft) * scale, + (y1 - srcTop) * scale); + ctx.bezierCurveTo((x2 - srcLeft) * scale, + (y2 - srcTop) * scale, + (x3 - srcLeft) * scale, + (y3 - srcTop) * scale, + (x4 - srcLeft) * scale, + (y4 - srcTop) * scale); + ctx.stroke(); +} + +function interp_cubic_coords(x1, x2, x3, x4, t) +{ + var ab = interp(x1, x2, t); + var bc = interp(x2, x3, t); + var cd = interp(x3, x4, t); + var abc = interp(ab, bc, t); + var bcd = interp(bc, cd, t); + var abcd = interp(abc, bcd, t); + return abcd; +} + +function cubicPartial(x1, y1, x2, y2, x3, y3, x4, y4, t1, t2) { + var ax = interp_cubic_coords(x1, x2, x3, x4, t1); + var ay = interp_cubic_coords(y1, y2, y3, y4, t1); + var ex = interp_cubic_coords(x1, x2, x3, x4, (t1*2+t2)/3); + var ey = interp_cubic_coords(y1, y2, y3, y4, (t1*2+t2)/3); + var fx = interp_cubic_coords(x1, x2, x3, x4, (t1+t2*2)/3); + var fy = interp_cubic_coords(y1, y2, y3, y4, (t1+t2*2)/3); + var dx = interp_cubic_coords(x1, x2, x3, x4, t2); + var dy = interp_cubic_coords(y1, y2, y3, y4, t2); + var mx = ex * 27 - ax * 8 - dx; + var my = ey * 27 - ay * 8 - dy; + var nx = fx * 27 - ax - dx * 8; + var ny = fy * 27 - ay - dy * 8; + var bx = (mx * 2 - nx) / 18; + var by = (my * 2 - ny) / 18; + var cx = (nx * 2 - mx) / 18; + var cy = (ny * 2 - my) / 18; + var array = [ + ax, ay, bx, by, cx, cy, dx, dy + ]; + return array; +} + +function drawCubicPartial(x1, y1, x2, y2, x3, y3, x4, y4, t1, t2) { + var a = cubicPartial(x1, y1, x2, y2, x3, y3, x4, y4, t1, t2); + var ax = a[0]; + var ay = a[1]; + var bx = a[2]; + var by = a[3]; + var cx = a[4]; + var cy = a[5]; + var dx = a[6]; + var dy = a[7]; + if (alreadyDrawnCubic(ax, ay, bx, by, cx, cy, dx, dy)) { + return; + } + ctx.beginPath(); + ctx.moveTo((ax - srcLeft) * scale, + (ay - srcTop) * scale); + ctx.bezierCurveTo((bx - srcLeft) * scale, + (by - srcTop) * scale, + (cx - srcLeft) * scale, + (cy - srcTop) * scale, + (dx - srcLeft) * scale, + (dy - srcTop) * scale); + ctx.stroke(); +} + +function drawCurve(c) { + switch (c.length) { + case 4: + drawLine(c[0], c[1], c[2], c[3]); + break; + case 6: + drawQuad(c[0], c[1], c[2], c[3], c[4], c[5]); + break; + case 8: + drawCubic(c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]); + break; + } +} + +function boundsWidth(pts) { + var min = pts[0]; + var max = pts[0]; + for (var idx = 2; idx < pts.length; idx += 2) { + min = Math.min(min, pts[idx]); + max = Math.max(max, pts[idx]); + } + return max - min; +} + +function boundsHeight(pts) { + var min = pts[1]; + var max = pts[1]; + for (var idx = 3; idx < pts.length; idx += 2) { + min = Math.min(min, pts[idx]); + max = Math.max(max, pts[idx]); + } + return max - min; +} + +function tangent(pts) { + var dx = pts[2] - pts[0]; + var dy = pts[3] - pts[1]; + if (dx == 0 && dy == 0 && pts.length > 4) { + dx = pts[4] - pts[0]; + dy = pts[5] - pts[1]; + if (dx == 0 && dy == 0 && pts.length > 6) { + dx = pts[6] - pts[0]; + dy = pts[7] - pts[1]; + } + } + return Math.atan2(-dy, dx); +} + +function hodograph(cubic) { + var hodo = []; + hodo[0] = 3 * (cubic[2] - cubic[0]); + hodo[1] = 3 * (cubic[3] - cubic[1]); + hodo[2] = 3 * (cubic[4] - cubic[2]); + hodo[3] = 3 * (cubic[5] - cubic[3]); + hodo[4] = 3 * (cubic[6] - cubic[4]); + hodo[5] = 3 * (cubic[7] - cubic[5]); + return hodo; +} + +function hodograph2(cubic) { + var quad = hodograph(cubic); + var hodo = []; + hodo[0] = 2 * (quad[2] - quad[0]); + hodo[1] = 2 * (quad[3] - quad[1]); + hodo[2] = 2 * (quad[4] - quad[2]); + hodo[3] = 2 * (quad[5] - quad[3]); + return hodo; +} + +function quadraticRootsReal(A, B, C, s) { + if (A == 0) { + if (B == 0) { + s[0] = 0; + return C == 0; + } + s[0] = -C / B; + return 1; + } + /* normal form: x^2 + px + q = 0 */ + var p = B / (2 * A); + var q = C / A; + var p2 = p * p; + if (p2 < q) { + return 0; + } + var sqrt_D = 0; + if (p2 > q) { + sqrt_D = sqrt(p2 - q); + } + s[0] = sqrt_D - p; + s[1] = -sqrt_D - p; + return 1 + s[0] != s[1]; +} + +function add_valid_ts(s, realRoots, t) { + var foundRoots = 0; + for (var index = 0; index < realRoots; ++index) { + var tValue = s[index]; + if (tValue >= 0 && tValue <= 1) { + for (var idx2 = 0; idx2 < foundRoots; ++idx2) { + if (t[idx2] != tValue) { + t[foundRoots++] = tValue; + } + } + } + } + return foundRoots; +} + +function quadraticRootsValidT(a, b, c, t) { + var s = []; + var realRoots = quadraticRootsReal(A, B, C, s); + var foundRoots = add_valid_ts(s, realRoots, t); + return foundRoots != 0; +} + +function find_cubic_inflections(cubic, tValues) { + var Ax = src[2] - src[0]; + var Ay = src[3] - src[1]; + var Bx = src[4] - 2 * src[2] + src[0]; + var By = src[5] - 2 * src[3] + src[1]; + var Cx = src[6] + 3 * (src[2] - src[4]) - src[0]; + var Cy = src[7] + 3 * (src[3] - src[5]) - src[1]; + return quadraticRootsValidT(Bx * Cy - By * Cx, (Ax * Cy - Ay * Cx), + Ax * By - Ay * Bx, tValues); +} + +function dxy_at_t(curve, type, t) { + var dxy = {}; + if (type == PATH_QUAD) { + var a = t - 1; + var b = 1 - 2 * t; + var c = t; + dxy.x = a * curve[0] + b * curve[2] + c * curve[4]; + dxy.y = a * curve[1] + b * curve[3] + c * curve[5]; + } else if (type == PATH_CUBIC) { + var one_t = 1 - t; + var a = curve[0]; + var b = curve[2]; + var c = curve[4]; + var d = curve[6]; + dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t); + a = curve[1]; + b = curve[3]; + c = curve[5]; + d = curve[7]; + dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t); + } + return dxy; +} + +function drawLabel(num, px, py) { + ctx.beginPath(); + ctx.arc(px, py, 8, 0, Math.PI*2, true); + ctx.closePath(); + ctx.strokeStyle = "rgba(0,0,0, 0.4)"; + ctx.lineWidth = num == 0 || num == 3 ? 2 : 1; + ctx.stroke(); + ctx.fillStyle = "black"; + ctx.font = "normal 10px Arial"; + // ctx.rotate(0.001); + ctx.fillText(num, px - 2, py + 3); + // ctx.rotate(-0.001); +} + +function drawLabelX(ymin, num, loc) { + var px = (loc - srcLeft) * scale; + var py = (ymin - srcTop) * scale - 20; + drawLabel(num, px, py); +} + +function drawLabelY(xmin, num, loc) { + var px = (xmin - srcLeft) * scale - 20; + var py = (loc - srcTop) * scale; + drawLabel(num, px, py); +} + +function drawHodoOrigin(hx, hy, hMinX, hMinY, hMaxX, hMaxY) { + ctx.beginPath(); + ctx.moveTo(hx, hy - 100); + ctx.lineTo(hx, hy); + ctx.strokeStyle = hMinY < 0 ? "green" : "blue"; + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(hx, hy); + ctx.lineTo(hx, hy + 100); + ctx.strokeStyle = hMaxY > 0 ? "green" : "blue"; + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(hx - 100, hy); + ctx.lineTo(hx, hy); + ctx.strokeStyle = hMinX < 0 ? "green" : "blue"; + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(hx, hy); + ctx.lineTo(hx + 100, hy); + ctx.strokeStyle = hMaxX > 0 ? "green" : "blue"; + ctx.stroke(); +} + +function scalexy(x, y, mag) { + var length = Math.sqrt(x * x + y * y); + return mag / length; +} + +function drawArrow(x, y, dx, dy) { + var dscale = scalexy(dx, dy, 1 / scale * 100); + dx *= dscale; + dy *= dscale; + ctx.beginPath(); + ctx.moveTo((x - srcLeft) * scale, (y - srcTop) * scale); + x += dx; + y += dy; + ctx.lineTo((x - srcLeft) * scale, (y - srcTop) * scale); + dx /= 10; + dy /= 10; + ctx.lineTo((x - dy - srcLeft) * scale, (y + dx - srcTop) * scale); + ctx.lineTo((x + dx * 2 - srcLeft) * scale, (y + dy * 2 - srcTop) * scale); + ctx.lineTo((x + dy - srcLeft) * scale, (y - dx - srcTop) * scale); + ctx.lineTo((x - srcLeft) * scale, (y - srcTop) * scale); + ctx.strokeStyle = "rgba(0,75,0, 0.4)"; + ctx.stroke(); +} + +function x_at_t(curve, t) { + var one_t = 1 - t; + if (curve.length == 4) { + return one_t * curve[0] + t * curve[2]; + } + var one_t2 = one_t * one_t; + var t2 = t * t; + if (curve.length == 6) { + return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4]; + } + var a = one_t2 * one_t; + var b = 3 * one_t2 * t; + var c = 3 * one_t * t2; + var d = t2 * t; + return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6]; +} + +function y_at_t(curve, t) { + var one_t = 1 - t; + if (curve.length == 4) { + return one_t * curve[1] + t * curve[3]; + } + var one_t2 = one_t * one_t; + var t2 = t * t; + if (curve.length == 6) { + return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5]; + } + var a = one_t2 * one_t; + var b = 3 * one_t2 * t; + var c = 3 * one_t * t2; + var d = t2 * t; + return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7]; +} + +function drawOrder(curve, label) { + var px = x_at_t(curve, 0.75); + var py = y_at_t(curve, 0.75); + var _px = (px - srcLeft) * scale; + var _py = (py - srcTop) * scale; + ctx.beginPath(); + ctx.arc(_px, _py, 15, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fillStyle = "white"; + ctx.fill(); + if (label == 'L') { + ctx.strokeStyle = "rgba(255,0,0, 1)"; + ctx.fillStyle = "rgba(255,0,0, 1)"; + } else { + ctx.strokeStyle = "rgba(0,0,255, 1)"; + ctx.fillStyle = "rgba(0,0,255, 1)"; + } + ctx.stroke(); + ctx.font = "normal 16px Arial"; + ctx.textAlign = "center"; + ctx.fillText(label, _px, _py + 5); + ctx.font = "normal 10px Arial"; +} + +function drawID(curve, id) { + var px = x_at_t(curve, 0.5); + var py = y_at_t(curve, 0.5); + var _px = (px - srcLeft) * scale; + var _py = (py - srcTop) * scale; + draw_id_at(id, _px, _py); +} + +function draw_id_at(id, _px, _py) { + ctx.beginPath(); + ctx.arc(_px, _py, 15, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fillStyle = "white"; + ctx.fill(); + ctx.strokeStyle = "rgba(127,127,0, 1)"; + ctx.fillStyle = "rgba(127,127,0, 1)"; + ctx.stroke(); + ctx.font = "normal 16px Arial"; + ctx.textAlign = "center"; + ctx.fillText(id, _px, _py + 5); + ctx.font = "normal 10px Arial"; +} + +function drawLinePartialID(id, x1, y1, x2, y2, t1, t2) { + var curve = [x1, y1, x2, y2]; + drawCurvePartialID(id, curve, t1, t2); +} + +function drawQuadPartialID(id, x1, y1, x2, y2, x3, y3, t1, t2) { + var curve = [x1, y1, x2, y2, x3, y3]; + drawCurvePartialID(id, curve, t1, t2); +} + +function drawCubicPartialID(id, x1, y1, x2, y2, x3, y3, x4, y4, t1, t2) { + var curve = [x1, y1, x2, y2, x3, y3, x4, y4]; + drawCurvePartialID(id, curve, t1, t2); +} + +function drawCurvePartialID(id, curve, t1, t2) { + var px = x_at_t(curve, (t1 + t2) / 2); + var py = y_at_t(curve, (t1 + t2) / 2); + var _px = (px - srcLeft) * scale; + var _py = (py - srcTop) * scale; + draw_id_at(id, _px, _py); +} + +function drawCurveSpecials(test, curve, type) { + if (pt_labels) { + drawPoints(curve, type, pt_labels == 2); + } + if (control_lines != 0) { + drawControlLines(curve, type, control_lines); + } + if (curve_t) { + drawPointAtT(curve, type); + } + if (draw_midpoint) { + var mid = pointAtT(curve, type, 0.5); + drawPoint(mid.x, mid.y, true); + } + if (draw_id) { + var id = idByCurve(test, curve, type); + if (id >= 0) { + drawID(curve, id); + } + } + if (type == PATH_LINE) { + return; + } + if (draw_deriviatives > 0) { + var d = dxy_at_t(curve, type, 0); + drawArrow(curve[0], curve[1], d.x, d.y); + if (draw_deriviatives == 2) { + d = dxy_at_t(curve, type, 1); + if (type == PATH_CUBIC) { + drawArrow(curve[6], curve[7], d.x, d.y); + } else { + drawArrow(curve[4], curve[5], d.x, d.y); + } + } + if (draw_midpoint) { + var mid = pointAtT(curve, type, 0.5); + d = dxy_at_t(curve, type, 0.5); + drawArrow(mid.x, mid.y, d.x, d.y); + } + } + if (type != PATH_CUBIC) { + return; + } + if (draw_hodo == 1 || draw_hodo == 2) { + var hodo = hodograph(curve); + var hMinX = Math.min(0, hodo[0], hodo[2], hodo[4]); + var hMinY = Math.min(0, hodo[1], hodo[3], hodo[5]); + var hMaxX = Math.max(0, hodo[0], hodo[2], hodo[4]); + var hMaxY = Math.max(0, hodo[1], hodo[3], hodo[5]); + var hScaleX = hMaxX - hMinX > 0 ? screenWidth / (hMaxX - hMinX) : 1; + var hScaleY = hMaxY - hMinY > 0 ? screenHeight / (hMaxY - hMinY) : 1; + var hUnit = Math.min(hScaleX, hScaleY); + hUnit /= 2; + var hx = xoffset - hMinX * hUnit; + var hy = yoffset - hMinY * hUnit; + ctx.moveTo(hx + hodo[0] * hUnit, hy + hodo[1] * hUnit); + ctx.quadraticCurveTo( + hx + hodo[2] * hUnit, hy + hodo[3] * hUnit, + hx + hodo[4] * hUnit, hy + hodo[5] * hUnit); + ctx.strokeStyle = "red"; + ctx.stroke(); + if (draw_hodo == 1) { + drawHodoOrigin(hx, hy, hMinX, hMinY, hMaxX, hMaxY); + } + } + if (draw_hodo == 3) { + var hodo = hodograph2(curve); + var hMinX = Math.min(0, hodo[0], hodo[2]); + var hMinY = Math.min(0, hodo[1], hodo[3]); + var hMaxX = Math.max(0, hodo[0], hodo[2]); + var hMaxY = Math.max(0, hodo[1], hodo[3]); + var hScaleX = hMaxX - hMinX > 0 ? screenWidth / (hMaxX - hMinX) : 1; + var hScaleY = hMaxY - hMinY > 0 ? screenHeight / (hMaxY - hMinY) : 1; + var hUnit = Math.min(hScaleX, hScaleY); + hUnit /= 2; + var hx = xoffset - hMinX * hUnit; + var hy = yoffset - hMinY * hUnit; + ctx.moveTo(hx + hodo[0] * hUnit, hy + hodo[1] * hUnit); + ctx.lineTo(hx + hodo[2] * hUnit, hy + hodo[3] * hUnit); + ctx.strokeStyle = "red"; + ctx.stroke(); + drawHodoOrigin(hx, hy, hMinX, hMinY, hMaxX, hMaxY); + } + if (draw_sequence) { + var ymin = Math.min(curve[1], curve[3], curve[5], curve[7]); + for (var i = 0; i < 8; i+= 2) { + drawLabelX(ymin, i >> 1, curve[i]); + } + var xmin = Math.min(curve[0], curve[2], curve[4], curve[6]); + for (var i = 1; i < 8; i+= 2) { + drawLabelY(xmin, i >> 1, curve[i]); + } + } +} + +function logCurves(test) { + for (curves in test) { + var curve = test[curves]; + dumpCurve(curve); + } +} + +function curveToString(curve) { + var str = "{{"; + for (i = 0; i < curve.length; i += 2) { + str += curve[i].toFixed(decimal_places) + "," + curve[i + 1].toFixed(decimal_places); + if (i < curve.length - 2) { + str += "}, {"; + } + } + str += "}}"; + return str; +} + +function dumpCurve(curve) { + console.log(curveToString(curve)); +} + +function draw(test, lines, title) { + ctx.fillStyle = "rgba(0,0,0, 0.1)"; + ctx.font = "normal 50px Arial"; + ctx.textAlign = "left"; + ctx.fillText(title, 50, 50); + ctx.font = "normal 10px Arial"; + ctx.lineWidth = "1.001"; "0.999"; + var secondPath = test.length; + var closeCount = 0; + logStart = -1; + logRange = 0; + // find last active rec type at this step + var curType = test[0]; + var curStep = 0; + var hasOp = false; + var lastActive = 0; + var lastAdd = 0; + var lastSect = 0; + var lastSort = 0; + var lastMark = 0; + activeCount = 0; + addCount = 0; + angleCount = 0; + opCount = 0; + sectCount = 0; + sortCount = 0; + markCount = 0; + activeMax = 0; + addMax = 0; + angleMax = 0; + opMax = 0; + sectMax = 0; + sectMax2 = 0; + sortMax = 0; + markMax = 0; + lastIndex = test.length - 3; + for (var tIndex = 0; tIndex < test.length; tIndex += 3) { + var recType = test[tIndex]; + if (!typeof recType == 'number' || recType < REC_TYPE_UNKNOWN || recType > REC_TYPE_LAST) { + console.log("unknown rec type: " + recType); + throw "stop execution"; + } + // if (curType == recType && curType != REC_TYPE_ADD) { + // continue; + // } + var inStepRange = step_limit == 0 || curStep < step_limit; + curType = recType; + if (recType == REC_TYPE_OP) { + hasOp = true; + continue; + } + if (recType == REC_TYPE_UNKNOWN) { + // these types do not advance step + continue; + } + var bumpStep = false; + var records = test[tIndex + 2]; + var fragType = records[0]; + if (recType == REC_TYPE_ADD) { + if (records.length != 2) { + console.log("expect only two elements: " + records.length); + throw "stop execution"; + } + if (fragType == ADD_MOVETO || fragType == ADD_CLOSE) { + continue; + } + ++addMax; + if (!draw_add || !inStepRange) { + continue; + } + lastAdd = tIndex; + ++addCount; + bumpStep = true; + } + if (recType == REC_TYPE_PATH && hasOp) { + secondPath = tIndex; + } + if (recType == REC_TYPE_ACTIVE) { + ++activeMax; + if (!draw_active || !inStepRange) { + continue; + } + lastActive = tIndex; + ++activeCount; + bumpStep = true; + } + if (recType == REC_TYPE_ACTIVE_OP) { + ++opMax; + if (!draw_op || !inStepRange) { + continue; + } + lastOp = tIndex; + ++opCount; + bumpStep = true; + } + if (recType == REC_TYPE_ANGLE) { + ++angleMax; + if (!draw_angle || !inStepRange) { + continue; + } + lastAngle = tIndex; + ++angleCount; + bumpStep = true; + } + if (recType == REC_TYPE_SECT) { + if (records.length != 2) { + console.log("expect only two elements: " + records.length); + throw "stop execution"; + } + ++sectMax; + var sectBump = 1; + switch (fragType) { + case INTERSECT_LINE: + case INTERSECT_QUAD_LINE: + case INTERSECT_QUAD: + case INTERSECT_SELF_CUBIC: + case INTERSECT_CUBIC_LINE: + case INTERSECT_CUBIC_QUAD: + case INTERSECT_CUBIC: + sectBump = 1; + break; + case INTERSECT_LINE_2: + case INTERSECT_QUAD_LINE_2: + case INTERSECT_QUAD_2: + case INTERSECT_CUBIC_LINE_2: + case INTERSECT_CUBIC_QUAD_2: + case INTERSECT_CUBIC_2: + sectBump = 2; + break; + case INTERSECT_LINE_NO: + case INTERSECT_QUAD_LINE_NO: + case INTERSECT_QUAD_NO: + case INTERSECT_SELF_CUBIC_NO: + case INTERSECT_CUBIC_LINE_NO: + case INTERSECT_CUBIC_QUAD_NO: + case INTERSECT_CUBIC_NO: + sectBump = 0; + break; + case INTERSECT_CUBIC_LINE_3: + case INTERSECT_CUBIC_QUAD_3: + case INTERSECT_CUBIC_3: + sectBump = 3; + break; + case INTERSECT_CUBIC_QUAD_4: + case INTERSECT_CUBIC_4: + sectBump = 4; + break; + default: + console.log("missing case " + records.length); + throw "stop execution"; + } + sectMax2 += sectBump; + if (draw_intersection <= 1 || !inStepRange) { + continue; + } + lastSect = tIndex; + sectCount += sectBump; + bumpStep = true; + } + if (recType == REC_TYPE_SORT) { + ++sortMax; + if (!draw_sort || !inStepRange) { + continue; + } + lastSort = tIndex; + ++sortCount; + bumpStep = true; + } + if (recType == REC_TYPE_MARK) { + ++markMax; + if (!draw_mark || !inStepRange) { + continue; + } + lastMark = tIndex; + ++markCount; + bumpStep = true; + } + if (bumpStep) { + lastIndex = tIndex; + logStart = test[tIndex + 1]; + logRange = records.length / 2; + ++curStep; + } + } + stepMax = (draw_add ? addMax : 0) + + (draw_active ? activeMax : 0) + + (draw_op ? opMax : 0) + + (draw_angle ? angleMax : 0) + + (draw_sort ? sortMax : 0) + + (draw_mark ? markMax : 0) + + (draw_intersection == 2 ? sectMax : draw_intersection == 3 ? sectMax2 : 0); + if (stepMax == 0) { + stepMax = addMax + activeMax + angleMax + opMax + sortMax + markMax; + } + drawnPts = []; + drawnLines = []; + drawnQuads = []; + drawnCubics = []; + focusXmin = focusYmin = Infinity; + focusXmax = focusYmax = -Infinity; + var pathIndex = 0; + var opLetter = 'S'; + for (var tIndex = lastIndex; tIndex >= 0; tIndex -= 3) { + var recType = test[tIndex]; + var records = test[tIndex + 2]; + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + if (!typeof fragType == 'number' || fragType < 1 || fragType > FRAG_TYPE_LAST) { + console.log("unknown in range frag type: " + fragType); + throw "stop execution"; + } + var frags = records[recordIndex + 1]; + focus_enabled = false; + switch (recType) { + case REC_TYPE_COMPUTED: + if (draw_computed == 0) { + continue; + } + ctx.lineWidth = 1; + ctx.strokeStyle = pathIndex == 0 ? "black" : "red"; + ctx.fillStyle = "blue"; + var drawThis = false; + switch (fragType) { + case PATH_QUAD: + if ((draw_computed & 5) == 1 || ((draw_computed & 4) != 0 + && (draw_computed & 1) == pathIndex)) { + drawQuad(frags[0], frags[1], frags[2], frags[3], + frags[4], frags[5]); + drawThis = true; + } + break; + case PATH_CUBIC: + if ((draw_computed & 6) == 2 || ((draw_computed & 4) != 0 + && (draw_computed & 1) != pathIndex)) { + drawCubic(frags[0], frags[1], frags[2], frags[3], + frags[4], frags[5], frags[6], frags[7]); + drawThis = true; + } + ++pathIndex; + break; + case COMPUTED_SET_1: + pathIndex = 0; + break; + case COMPUTED_SET_2: + pathIndex = 1; + break; + default: + console.log("unknown REC_TYPE_COMPUTED frag type: " + fragType); + throw "stop execution"; + } + if (!drawThis || collect_bounds) { + break; + } + drawCurveSpecials(test, frags, fragType); + break; + case REC_TYPE_PATH: + if (!draw_path) { + continue; + } + var firstPath = tIndex < secondPath; + if ((draw_path & (firstPath ? 1 : 2)) == 0) { + continue; + } + ctx.lineWidth = 1; + ctx.strokeStyle = firstPath ? "black" : "red"; + ctx.fillStyle = "blue"; + switch (fragType) { + case PATH_LINE: + drawLine(frags[0], frags[1], frags[2], frags[3]); + break; + case PATH_QUAD: + drawQuad(frags[0], frags[1], frags[2], frags[3], + frags[4], frags[5]); + break; + case PATH_CUBIC: + drawCubic(frags[0], frags[1], frags[2], frags[3], + frags[4], frags[5], frags[6], frags[7]); + break; + default: + console.log("unknown REC_TYPE_PATH frag type: " + fragType); + throw "stop execution"; + } + if (collect_bounds) { + break; + } + drawCurveSpecials(test, frags, fragType); + break; + case REC_TYPE_OP: + switch (fragType) { + case OP_INTERSECT: opLetter = 'I'; break; + case OP_DIFFERENCE: opLetter = 'D'; break; + case OP_UNION: opLetter = 'U'; break; + case OP_XOR: opLetter = 'X'; break; + default: + console.log("unknown REC_TYPE_OP frag type: " + fragType); + throw "stop execution"; + } + break; + case REC_TYPE_ACTIVE: + if (!draw_active || (step_limit > 0 && tIndex < lastActive)) { + continue; + } + var x1 = frags[SPAN_X1]; + var y1 = frags[SPAN_Y1]; + var x2 = frags[SPAN_X2]; + var y2 = frags[SPAN_Y2]; + var x3, y3, x3, y4, t1, t2; + ctx.lineWidth = 3; + ctx.strokeStyle = "rgba(0,0,255, 0.3)"; + focus_enabled = true; + switch (fragType) { + case ACTIVE_LINE_SPAN: + t1 = frags[SPAN_L_T]; + t2 = frags[SPAN_L_TEND]; + drawLinePartial(x1, y1, x2, y2, t1, t2); + if (draw_id) { + drawLinePartialID(frags[0], x1, y1, x2, y2, t1, t2); + } + break; + case ACTIVE_QUAD_SPAN: + x3 = frags[SPAN_X3]; + y3 = frags[SPAN_Y3]; + t1 = frags[SPAN_Q_T]; + t2 = frags[SPAN_Q_TEND]; + drawQuadPartial(x1, y1, x2, y2, x3, y3, t1, t2); + if (draw_id) { + drawQuadPartialID(frags[0], x1, y1, x2, y2, x3, y3, t1, t2); + } + break; + case ACTIVE_CUBIC_SPAN: + x3 = frags[SPAN_X3]; + y3 = frags[SPAN_Y3]; + x4 = frags[SPAN_X4]; + y4 = frags[SPAN_Y4]; + t1 = frags[SPAN_C_T]; + t2 = frags[SPAN_C_TEND]; + drawCubicPartial(x1, y1, x2, y2, x3, y3, x4, y4, t1, t2); + if (draw_id) { + drawCubicPartialID(frags[0], x1, y1, x2, y2, x3, y3, x4, y4, t1, t2); + } + break; + default: + console.log("unknown REC_TYPE_ACTIVE frag type: " + fragType); + throw "stop execution"; + } + break; + case REC_TYPE_ACTIVE_OP: + if (!draw_op || (step_limit > 0 && tIndex < lastOp)) { + continue; + } + focus_enabled = true; + ctx.lineWidth = 3; + var activeSpan = frags[7] == "1"; + ctx.strokeStyle = activeSpan ? "rgba(45,160,0, 0.3)" : "rgba(255,45,0, 0.5)"; + var curve = curvePartialByID(test, frags[0], frags[1], frags[2]); + drawCurve(curve); + if (draw_op > 1) { + drawArc(curve, false, frags[3], frags[4]); + drawArc(curve, true, frags[5], frags[6]); + } + break; + case REC_TYPE_ADD: + if (!draw_add) { + continue; + } + ctx.lineWidth = 3; + ctx.strokeStyle = closeCount == 0 ? "rgba(0,0,255, 0.3)" + : closeCount == 1 ? "rgba(0,127,0, 0.3)" + : closeCount == 2 ? "rgba(0,127,127, 0.3)" + : closeCount == 3 ? "rgba(127,127,0, 0.3)" + : "rgba(127,0,127, 0.3)"; + focus_enabled = true; + switch (fragType) { + case ADD_MOVETO: + break; + case ADD_LINETO: + if (step_limit == 0 || tIndex >= lastAdd) { + drawLine(frags[0], frags[1], frags[2], frags[3]); + } + break; + case ADD_QUADTO: + if (step_limit == 0 || tIndex >= lastAdd) { + drawQuad(frags[0], frags[1], frags[2], frags[3], frags[4], frags[5]); + } + break; + case ADD_CUBICTO: + if (step_limit == 0 || tIndex >= lastAdd) { + drawCubic(frags[0], frags[1], frags[2], frags[3], + frags[4], frags[5], frags[6], frags[7]); + } + break; + case ADD_CLOSE: + ++closeCount; + break; + case ADD_FILL: + break; + default: + console.log("unknown REC_TYPE_ADD frag type: " + fragType); + throw "stop execution"; + } + break; + case REC_TYPE_ANGLE: + if (!draw_angle || (step_limit > 0 && tIndex < lastAngle)) { + continue; + } + if (fragType != ANGLE_AFTER && fragType != ANGLE_AFTER2) { + continue; + } + focus_enabled = true; + ctx.lineWidth = 3; + ctx.strokeStyle = "rgba(127,45,127, 0.3)"; + var leftCurve, midCurve, rightCurve; + if (fragType == ANGLE_AFTER) { + leftCurve = curvePartialByID(test, frags[0], frags[3], frags[4]); + midCurve = curvePartialByID(test, frags[5], frags[8], frags[9]); + rightCurve = curvePartialByID(test, frags[10], frags[13], frags[14]); + } else { + leftCurve = curvePartialByID(test, frags[0], frags[4], frags[5]); + midCurve = curvePartialByID(test, frags[6], frags[10], frags[11]); + rightCurve = curvePartialByID(test, frags[12], frags[16], frags[17]); + } + drawCurve(leftCurve); + drawCurve(rightCurve); + var inBetween = frags[fragType == ANGLE_AFTER ? 15 : 18] == "T"; + ctx.strokeStyle = inBetween ? "rgba(0,160,45, 0.3)" : "rgba(255,0,45, 0.5)"; + drawCurve(midCurve); + if (draw_angle > 1) { + drawOrder(leftCurve, 'L'); + drawOrder(rightCurve, 'R'); + } + break; + case REC_TYPE_SECT: + if (!draw_intersection) { + continue; + } + if (draw_intersection != 1 && (step_limit > 0 && tIndex < lastSect)) { + continue; + } + // draw_intersection == 1 : show all + // draw_intersection == 2 : step == 0 ? show all : show intersection line #step + // draw_intersection == 3 : step == 0 ? show all : show intersection #step + ctx.lineWidth = 1; + ctx.strokeStyle = "rgba(0,0,255, 0.3)"; + ctx.fillStyle = "blue"; + focus_enabled = true; + var f = []; + var c1s; + var c1l; + var c2s; + var c2l; + switch (fragType) { + case INTERSECT_LINE: + f.push(5, 6, 0, 7); + c1s = 1; c1l = 4; c2s = 8; c2l = 4; + break; + case INTERSECT_LINE_2: + f.push(5, 6, 0, 10); + f.push(8, 9, 7, 15); + c1s = 1; c1l = 4; c2s = 11; c2l = 4; + break; + case INTERSECT_LINE_NO: + c1s = 0; c1l = 4; c2s = 4; c2l = 4; + break; + case INTERSECT_QUAD_LINE: + f.push(7, 8, 0, 9); + c1s = 1; c1l = 6; c2s = 10; c2l = 4; + break; + case INTERSECT_QUAD_LINE_2: + f.push(7, 8, 0, 12); + f.push(10, 11, 9, 17); + c1s = 1; c1l = 6; c2s = 13; c2l = 4; + break; + case INTERSECT_QUAD_LINE_NO: + c1s = 0; c1l = 6; c2s = 6; c2l = 4; + break; + case INTERSECT_QUAD: + f.push(7, 8, 0, 9); + c1s = 1; c1l = 6; c2s = 10; c2l = 6; + break; + case INTERSECT_QUAD_2: + f.push(7, 8, 0, 12); + f.push(10, 11, 9, 19); + c1s = 1; c1l = 6; c2s = 13; c2l = 6; + break; + case INTERSECT_QUAD_NO: + c1s = 0; c1l = 6; c2s = 6; c2l = 6; + break; + case INTERSECT_SELF_CUBIC: + f.push(9, 10, 0, 11); + c1s = 1; c1l = 8; c2s = 0; c2l = 0; + break; + case INTERSECT_SELF_CUBIC_NO: + c1s = 0; c1l = 8; c2s = 0; c2l = 0; + break; + case INTERSECT_CUBIC_LINE: + f.push(9, 10, 0, 11); + c1s = 1; c1l = 8; c2s = 12; c2l = 4; + break; + case INTERSECT_CUBIC_LINE_2: + f.push(9, 10, 0, 14); + f.push(12, 13, 11, 19); + c1s = 1; c1l = 8; c2s = 15; c2l = 4; + break; + case INTERSECT_CUBIC_LINE_3: + f.push(9, 10, 0, 17); + f.push(12, 13, 11, 22); + f.push(15, 16, 14, 23); + c1s = 1; c1l = 8; c2s = 18; c2l = 4; + break; + case INTERSECT_CUBIC_QUAD_NO: + c1s = 0; c1l = 8; c2s = 8; c2l = 6; + break; + case INTERSECT_CUBIC_QUAD: + f.push(9, 10, 0, 11); + c1s = 1; c1l = 8; c2s = 12; c2l = 6; + break; + case INTERSECT_CUBIC_QUAD_2: + f.push(9, 10, 0, 14); + f.push(12, 13, 11, 21); + c1s = 1; c1l = 8; c2s = 15; c2l = 6; + break; + case INTERSECT_CUBIC_QUAD_3: + f.push(9, 10, 0, 17); + f.push(12, 13, 11, 24); + f.push(15, 16, 14, 25); + c1s = 1; c1l = 8; c2s = 18; c2l = 6; + break; + case INTERSECT_CUBIC_QUAD_4: + f.push(9, 10, 0, 20); + f.push(12, 13, 11, 27); + f.push(15, 16, 14, 28); + f.push(18, 19, 17, 29); + c1s = 1; c1l = 8; c2s = 21; c2l = 6; + break; + case INTERSECT_CUBIC_LINE_NO: + c1s = 0; c1l = 8; c2s = 8; c2l = 4; + break; + case INTERSECT_CUBIC: + f.push(9, 10, 0, 11); + c1s = 1; c1l = 8; c2s = 12; c2l = 8; + break; + case INTERSECT_CUBIC_2: + f.push(9, 10, 0, 14); + f.push(12, 13, 11, 23); + c1s = 1; c1l = 8; c2s = 15; c2l = 8; + break; + case INTERSECT_CUBIC_3: + f.push(9, 10, 0, 17); + f.push(12, 13, 11, 26); + f.push(15, 16, 14, 27); + c1s = 1; c1l = 8; c2s = 18; c2l = 8; + break; + case INTERSECT_CUBIC_4: + f.push(9, 10, 0, 20); + f.push(12, 13, 11, 29); + f.push(15, 16, 14, 30); + f.push(18, 19, 17, 31); + c1s = 1; c1l = 8; c2s = 21; c2l = 8; + break; + case INTERSECT_CUBIC_NO: + c1s = 0; c1l = 8; c2s = 8; c2l = 8; + break; + default: + console.log("unknown REC_TYPE_SECT frag type: " + fragType); + throw "stop execution"; + } + if (draw_intersection != 1) { + var id = -1; + var curve; + switch (c1l) { + case 4: + drawLine(frags[c1s], frags[c1s + 1], frags[c1s + 2], frags[c1s + 3]); + if (draw_id) { + curve = [frags[c1s], frags[c1s + 1], frags[c1s + 2], frags[c1s + 3]]; + id = idByCurve(test, curve, PATH_LINE); + } + break; + case 6: + drawQuad(frags[c1s], frags[c1s + 1], frags[c1s + 2], frags[c1s + 3], + frags[c1s + 4], frags[c1s + 5]); + if (draw_id) { + curve = [frags[c1s], frags[c1s + 1], frags[c1s + 2], frags[c1s + 3], + frags[c1s + 4], frags[c1s + 5]]; + id = idByCurve(test, curve, PATH_QUAD); + } + break; + case 8: + drawCubic(frags[c1s], frags[c1s + 1], frags[c1s + 2], frags[c1s + 3], + frags[c1s + 4], frags[c1s + 5], frags[c1s + 6], frags[c1s + 7]); + if (draw_id) { + curve = [frags[c1s], frags[c1s + 1], frags[c1s + 2], frags[c1s + 3], + frags[c1s + 4], frags[c1s + 5], frags[c1s + 6], frags[c1s + 7]]; + id = idByCurve(test, curve, PATH_CUBIC); + } + break; + } + if (id >= 0) { + drawID(curve, id); + } + id = -1; + switch (c2l) { + case 0: + break; + case 4: + drawLine(frags[c2s], frags[c2s + 1], frags[c2s + 2], frags[c2s + 3]); + if (draw_id) { + curve = [frags[c2s], frags[c2s + 1], frags[c2s + 2], frags[c2s + 3]]; + id = idByCurve(test, curve, PATH_LINE); + } + break; + case 6: + drawQuad(frags[c2s], frags[c2s + 1], frags[c2s + 2], frags[c2s + 3], + frags[c2s + 4], frags[c2s + 5]); + if (draw_id) { + curve = [frags[c2s], frags[c2s + 1], frags[c2s + 2], frags[c2s + 3], + frags[c2s + 4], frags[c2s + 5]]; + id = idByCurve(test, curve, PATH_QUAD); + } + break; + case 8: + drawCubic(frags[c2s], frags[c2s + 1], frags[c2s + 2], frags[c2s + 3], + frags[c2s + 4], frags[c2s + 5], frags[c2s + 6], frags[c2s + 7]); + if (draw_id) { + curve = [frags[c2s], frags[c2s + 1], frags[c2s + 2], frags[c2s + 3], + frags[c2s + 4], frags[c2s + 5], frags[c2s + 6], frags[c2s + 7]]; + id = idByCurve(test, curve, PATH_CUBIC); + } + break; + } + if (id >= 0) { + drawID(curve, id); + } + } + if (collect_bounds) { + break; + } + for (var idx = 0; idx < f.length; idx += 4) { + if (draw_intersection != 3 || idx == lastSect - tIndex) { + drawPoint(frags[f[idx]], frags[f[idx + 1]], true); + } + } + if (!draw_intersectT) { + break; + } + ctx.fillStyle = "red"; + for (var idx = 0; idx < f.length; idx += 4) { + if (draw_intersection != 3 || idx == lastSect - tIndex) { + drawTAtPointUp(frags[f[idx]], frags[f[idx + 1]], frags[f[idx + 2]]); + drawTAtPointDown(frags[f[idx]], frags[f[idx + 1]], frags[f[idx + 3]]); + } + } + break; + case REC_TYPE_SORT: + if (!draw_sort || (step_limit > 0 && tIndex < lastSort)) { + continue; + } + ctx.lineWidth = 3; + ctx.strokeStyle = "rgba(127,127,0, 0.5)"; + focus_enabled = true; + switch (fragType) { + case SORT_UNARY: + case SORT_BINARY: + var curve = curvePartialByID(test, frags[0], frags[6], frags[8]); + drawCurve(curve); + break; + default: + console.log("unknown REC_TYPE_SORT frag type: " + fragType); + throw "stop execution"; + } + break; + case REC_TYPE_MARK: + if (!draw_mark || (step_limit > 0 && tIndex < lastMark)) { + continue; + } + ctx.lineWidth = 3; + ctx.strokeStyle = fragType >= MARK_DONE_LINE ? + "rgba(127,0,127, 0.5)" : "rgba(127,127,0, 0.5)"; + focus_enabled = true; + switch (fragType) { + case MARK_LINE: + case MARK_DONE_LINE: + case MARK_UNSORTABLE_LINE: + case MARK_SIMPLE_LINE: + case MARK_SIMPLE_DONE_LINE: + case MARK_DONE_UNARY_LINE: + drawLinePartial(frags[1], frags[2], frags[3], frags[4], + frags[5], frags[9]); + if (draw_id) { + drawLinePartialID(frags[0], frags[1], frags[2], frags[3], frags[4], + frags[5], frags[9]); + } + break; + case MARK_QUAD: + case MARK_DONE_QUAD: + case MARK_UNSORTABLE_QUAD: + case MARK_SIMPLE_QUAD: + case MARK_SIMPLE_DONE_QUAD: + case MARK_DONE_UNARY_QUAD: + drawQuadPartial(frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6], frags[7], frags[11]); + if (draw_id) { + drawQuadPartialID(frags[0], frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6], frags[7], frags[11]); + } + break; + case MARK_CUBIC: + case MARK_DONE_CUBIC: + case MARK_UNSORTABLE_CUBIC: + case MARK_SIMPLE_CUBIC: + case MARK_SIMPLE_DONE_CUBIC: + case MARK_DONE_UNARY_CUBIC: + drawCubicPartial(frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6], frags[7], frags[8], frags[9], frags[13]); + if (draw_id) { + drawCubicPartialID(frags[0], frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6], frags[7], frags[8], frags[9], frags[13]); + } + break; + case MARK_ANGLE_LAST: + // FIXME: ignored for now + break; + default: + console.log("unknown REC_TYPE_MARK frag type: " + fragType); + throw "stop execution"; + } + break; + default: + continue; + } + } + switch (recType) { + case REC_TYPE_SORT: + if (!draw_sort || (step_limit > 0 && tIndex < lastSort)) { + break; + } + var angles = []; // use tangent lines to describe arcs + var windFrom = []; + var windTo = []; + var opp = []; + var minXY = Number.MAX_VALUE; + var partial; + focus_enabled = true; + var someUnsortable = false; + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + var frags = records[recordIndex + 1]; + var unsortable = (fragType == SORT_UNARY && frags[14]) || + (fragType == SORT_BINARY && frags[16]); + someUnsortable |= unsortable; + switch (fragType) { + case SORT_UNARY: + case SORT_BINARY: + partial = curvePartialByID(test, frags[0], frags[6], frags[8]); + break; + default: + console.log("unknown REC_TYPE_SORT frag type: " + fragType); + throw "stop execution"; + } + var dx = boundsWidth(partial); + var dy = boundsHeight(partial); + minXY = Math.min(minXY, dx * dx + dy * dy); + if (collect_bounds) { + continue; + } + angles.push(tangent(partial)); + var from = frags[12]; + var to = frags[12]; + var sgn = frags[10]; + if (sgn < 0) { + from -= frags[11]; + } else if (sgn > 0) { + to -= frags[11]; + } + windFrom.push(from + (unsortable ? "!" : "")); + windTo.push(to + (unsortable ? "!" : "")); + opp.push(fragType == SORT_BINARY); + if (draw_sort == 1) { + drawOrder(partial, frags[12]); + } else { + drawOrder(partial, (recordIndex / 2) + 1); + } + } + var radius = Math.sqrt(minXY) / 2 * scale; + radius = Math.min(50, radius); + var scaledRadius = radius / scale; + var centerX = partial[0]; + var centerY = partial[1]; + if (collect_bounds) { + if (focus_enabled) { + focusXmin = Math.min(focusXmin, centerX - scaledRadius); + focusYmin = Math.min(focusYmin, centerY - scaledRadius); + focusXmax = Math.max(focusXmax, centerX + scaledRadius); + focusYmax = Math.max(focusYmax, centerY + scaledRadius); + } + break; + } + break; + default: + break; + } + } + if (collect_bounds) { + return; + } + if (draw_log && logStart >= 0) { + ctx.font = "normal 10px Arial"; + ctx.textAlign = "left"; + ctx.beginPath(); + var top = screenHeight - 20 - (logRange + 2) * 10; + ctx.rect(50, top, screenWidth - 100, (logRange + 2) * 10); + ctx.fillStyle = "white"; + ctx.fill(); + ctx.fillStyle = "rgba(0,0,0, 0.5)"; + if (logStart > 0) { + ctx.fillText(lines[logStart - 1], 50, top + 8); + } + ctx.fillStyle = "black"; + for (var idx = 0; idx < logRange; ++idx) { + ctx.fillText(lines[logStart + idx], 50, top + 18 + 10 * idx); + } + ctx.fillStyle = "rgba(0,0,0, 0.5)"; + if (logStart + logRange < lines.length) { + ctx.fillText(lines[logStart + logRange], 50, top + 18 + 10 * logRange); + } + } + if (draw_legend) { + var pos = 0; + var drawSomething = draw_add | draw_active | draw_sort | draw_mark; + // drawBox(pos++, "yellow", "black", opLetter, true, ''); + drawBox(pos++, "rgba(0,0,255, 0.3)", "black", draw_intersection > 1 ? sectCount : sectMax2, draw_intersection, intersectionKey); + drawBox(pos++, "rgba(0,0,255, 0.3)", "black", draw_add ? addCount : addMax, draw_add, addKey); + drawBox(pos++, "rgba(0,0,255, 0.3)", "black", draw_active ? activeCount : activeMax, draw_active, activeKey); + drawBox(pos++, "rgba(127,127,0, 0.3)", "black", draw_angle ? angleCount : angleMax, draw_angle, angleKey); + drawBox(pos++, "rgba(127,127,0, 0.3)", "black", draw_op ? opCount : opMax, draw_op, opKey); + drawBox(pos++, "rgba(127,127,0, 0.3)", "black", draw_sort ? sortCount : sortMax, draw_sort, sortKey); + drawBox(pos++, "rgba(127,0,127, 0.3)", "black", draw_mark ? markCount : markMax, draw_mark, markKey); + drawBox(pos++, "black", "white", + (new Array('P', 'P1', 'P2', 'P'))[draw_path], draw_path != 0, pathKey); + drawBox(pos++, "rgba(0,63,0, 0.7)", "white", + (new Array('Q', 'Q', 'C', 'QC', 'Qc', 'Cq'))[draw_computed], + draw_computed != 0, computedKey); + drawBox(pos++, "green", "black", step_limit, drawSomething, ''); + drawBox(pos++, "green", "black", stepMax, drawSomething, ''); + drawBox(pos++, "rgba(255,0,0, 0.6)", "black", lastIndex, drawSomething & draw_log, ''); + drawBox(pos++, "rgba(255,0,0, 0.6)", "black", test.length - 1, drawSomething & draw_log, ''); + if (curve_t) { + drawCurveTControl(); + } + ctx.font = "normal 20px Arial"; + ctx.fillStyle = "rgba(0,0,0, 0.3)"; + ctx.textAlign = "right"; + ctx.fillText(scale.toFixed(decimal_places) + 'x' , screenWidth - 10, screenHeight - 5); + } + if (draw_hints) { + ctx.font = "normal 10px Arial"; + ctx.fillStyle = "rgba(0,0,0, 0.5)"; + ctx.textAlign = "right"; + var y = 4; + ctx.fillText("control lines : " + controlLinesKey, ctx.screenWidthwidth - 10, pos * 50 + y++ * 10); + ctx.fillText("curve t : " + curveTKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("deriviatives : " + deriviativesKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("intersect t : " + intersectTKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("hodo : " + hodoKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("log : " + logKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("log curve : " + logCurvesKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("mid point : " + midpointKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("points : " + ptsKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("sequence : " + sequenceKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("xy : " + xyKey, screenWidth - 10, pos * 50 + y++ * 10); + } +} + +function drawBox(y, backC, foreC, str, enable, label) { + ctx.beginPath(); + ctx.fillStyle = backC; + ctx.rect(screenWidth - 40, y * 50 + 10, 40, 30); + ctx.fill(); + ctx.font = "normal 16px Arial"; + ctx.fillStyle = foreC; + ctx.textAlign = "center"; + ctx.fillText(str, screenWidth - 20, y * 50 + 32); + if (!enable) { + ctx.fillStyle = "rgba(255,255,255, 0.5)"; + ctx.fill(); + } + if (label != '') { + ctx.font = "normal 9px Arial"; + ctx.fillStyle = "black"; + ctx.fillText(label, screenWidth - 47, y * 50 + 40); + } +} + +function drawCurveTControl() { + ctx.lineWidth = 2; + ctx.strokeStyle = "rgba(0,0,0, 0.3)"; + ctx.beginPath(); + ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80); + ctx.stroke(); + var ty = 40 + curveT * (screenHeight - 80); + ctx.beginPath(); + ctx.moveTo(screenWidth - 80, ty); + ctx.lineTo(screenWidth - 85, ty - 5); + ctx.lineTo(screenWidth - 85, ty + 5); + ctx.lineTo(screenWidth - 80, ty); + ctx.fillStyle = "rgba(0,0,0, 0.6)"; + ctx.fill(); + var num = curveT.toFixed(decimal_places); + ctx.font = "normal 10px Arial"; + ctx.textAlign = "left"; + ctx.fillText(num, screenWidth - 78, ty); +} + +function ptInTControl() { + var e = window.event; + var tgt = e.target || e.srcElement; + var left = tgt.offsetLeft; + var top = tgt.offsetTop; + var x = (e.clientX - left); + var y = (e.clientY - top); + if (x < screenWidth - 80 || x > screenWidth - 50) { + return false; + } + if (y < 40 || y > screenHeight - 80) { + return false; + } + curveT = (y - 40) / (screenHeight - 120); + if (curveT < 0 || curveT > 1) { + throw "stop execution"; + } + return true; +} + +function drawTop() { + if (tests[testIndex] == null) { + var str = testDivs[testIndex].textContent; + parse_all(str); + var title = testDivs[testIndex].id.toString(); + testTitles[testIndex] = title; + } + init(tests[testIndex]); + redraw(); +} + +function redraw() { + if (focus_on_selection) { + collect_bounds = true; + draw(tests[testIndex], testLines[testIndex], testTitles[testIndex]); + collect_bounds = false; + if (focusXmin < focusXmax && focusYmin < focusYmax) { + setScale(focusXmin, focusXmax, focusYmin, focusYmax); + } + } + ctx.beginPath(); + ctx.fillStyle = "white"; + ctx.rect(0, 0, screenWidth, screenHeight); + ctx.fill(); + draw(tests[testIndex], testLines[testIndex], testTitles[testIndex]); +} + +function dumpCurvePartial(test, id, t0, t1) { + var curve = curveByID(test, id); + var name = ["line", "quad", "cubic"][curve.length / 2 - 2]; + console.log("id=" + id + " " + name + "=" + curveToString(curve) + + " t0=" + t0 + " t1=" + t1 + + " partial=" + curveToString(curvePartialByID(test, id, t0, t1))); +} + +function dumpAngleTest(test, id, t0, t1) { + var curve = curveByID(test, id); + console.log(" { {" + curveToString(curve) + "}, " + + curve.length / 2 + ", " + t0 + ", " + t1 + ", {} }, //"); +} + +function dumpLogToConsole() { + if (logStart < 0) { + return; + } + var test = tests[testIndex]; + var recType = REC_TYPE_UNKNOWN; + var records; + for (var index = 0; index < test.length; index += 3) { + var lastLineNo = test[index + 1]; + if (lastLineNo >= logStart && lastLineNo < logStart + logRange) { + recType = test[index]; + records = test[index + 2]; + break; + } + } + if (recType == REC_TYPE_UNKNOWN) { + return; + } + var lines = testLines[testIndex]; + for (var idx = 0; idx < logRange; ++idx) { + var line = lines[logStart + idx]; + console.log(line); + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + var frags = records[recordIndex + 1]; + if (recType == REC_TYPE_ANGLE && fragType == ANGLE_AFTER) { + dumpCurvePartial(test, frags[0], frags[3], frags[4]); + dumpCurvePartial(test, frags[5], frags[8], frags[9]); + dumpCurvePartial(test, frags[10], frags[13], frags[14]); + console.log("\nstatic IntersectData intersectDataSet[] = {"); + dumpAngleTest(test, frags[0], frags[3], frags[4]); + dumpAngleTest(test, frags[5], frags[8], frags[9]); + dumpAngleTest(test, frags[10], frags[13], frags[14]); + console.log("};"); + } else if (recType == REC_TYPE_ANGLE && fragType == ANGLE_AFTER2) { + dumpCurvePartial(test, frags[0], frags[4], frags[5]); + dumpCurvePartial(test, frags[6], frags[10], frags[11]); + dumpCurvePartial(test, frags[12], frags[16], frags[17]); + console.log("\nstatic IntersectData intersectDataSet[] = { //"); + dumpAngleTest(test, frags[0], frags[4], frags[5]); + dumpAngleTest(test, frags[6], frags[10], frags[11]); + dumpAngleTest(test, frags[12], frags[16], frags[17]); + console.log("}; //"); + } + } + } +} + +var activeKey = 'a'; +var pathKey = 'b'; +var pathBackKey = 'B'; +var centerKey = 'c'; +var addKey = 'd'; +var deriviativesKey = 'f'; +var angleKey = 'g'; +var angleBackKey = 'G'; +var hodoKey = 'h'; +var intersectionKey = 'i'; +var intersectionBackKey = 'I'; +var sequenceKey = 'j'; +var midpointKey = 'k'; +var logKey = 'l'; +var logToConsoleKey = 'L'; +var markKey = 'm'; +var sortKey = 'o'; +var opKey = 'p'; +var opBackKey = 'P'; +var computedKey = 'q'; +var computedBackKey = 'Q'; +var stepKey = 's'; +var stepBackKey = 'S'; +var intersectTKey = 't'; +var curveTKey = 'u'; +var controlLinesBackKey = 'V'; +var controlLinesKey = 'v'; +var ptsKey = 'x'; +var xyKey = 'y'; +var logCurvesKey = 'z'; +var focusKey = '`'; +var idKey = '.'; +var retinaKey = '\\'; + +function doKeyPress(evt) { + var char = String.fromCharCode(evt.charCode); + var focusWasOn = false; + switch (char) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + decimal_places = char - '0'; + redraw(); + break; + case activeKey: + draw_active ^= true; + redraw(); + break; + case addKey: + draw_add ^= true; + redraw(); + break; + case angleKey: + draw_angle = (draw_angle + 1) % 3; + redraw(); + break; + case angleBackKey: + draw_angle = (draw_angle + 2) % 3; + redraw(); + break; + case centerKey: + setScale(xmin, xmax, ymin, ymax); + redraw(); + break; + case controlLinesBackKey: + control_lines = (control_lines + 3) % 4; + redraw(); + break; + case controlLinesKey: + control_lines = (control_lines + 1) % 4; + redraw(); + break; + case computedBackKey: + draw_computed = (draw_computed + 5) % 6; + redraw(); + break; + case computedKey: + draw_computed = (draw_computed + 1) % 6; + redraw(); + break; + case curveTKey: + curve_t ^= true; + if (curve_t) { + draw_legend = true; + } + redraw(); + break; + case deriviativesKey: + draw_deriviatives = (draw_deriviatives + 1) % 3; + redraw(); + break; + case focusKey: + focus_on_selection ^= true; + setScale(xmin, xmax, ymin, ymax); + redraw(); + break; + case hodoKey: + draw_hodo = (draw_hodo + 1) % 4; + redraw(); + break; + case idKey: + draw_id ^= true; + redraw(); + break; + case intersectionBackKey: + draw_intersection = (draw_intersection + 3) % 4; + redraw(); + break; + case intersectionKey: + draw_intersection = (draw_intersection + 1) % 4; + redraw(); + break; + case intersectTKey: + draw_intersectT ^= true; + redraw(); + break; + case logCurvesKey: + logCurves(tests[testIndex]); + break; + case logKey: + draw_log ^= true; + redraw(); + break; + case logToConsoleKey: + if (draw_log) { + dumpLogToConsole(); + } + break; + case markKey: + draw_mark ^= true; + redraw(); + break; + case midpointKey: + draw_midpoint ^= true; + redraw(); + break; + case opKey: + draw_op = (draw_op + 1) % 3; + redraw(); + break; + case opBackKey: + draw_op = (draw_op + 2) % 3; + redraw(); + break; + case pathKey: + draw_path = (draw_path + 1) % 4; + redraw(); + break; + case pathBackKey: + draw_path = (draw_path + 3) % 4; + redraw(); + break; + case ptsKey: + pt_labels = (pt_labels + 1) % 3; + redraw(); + break; + case retinaKey: + retina_scale ^= true; + drawTop(); + break; + case sequenceKey: + draw_sequence ^= true; + redraw(); + break; + case sortKey: + draw_sort = (draw_sort + 1) % 3; + drawTop(); + break; + case stepKey: + step_limit++; + if (step_limit > stepMax) { + step_limit = stepMax; + } + redraw(); + break; + case stepBackKey: + step_limit--; + if (step_limit < 0) { + step_limit = 0; + } + redraw(); + break; + case xyKey: + debug_xy = (debug_xy + 1) % 3; + redraw(); + break; + case '-': + focusWasOn = focus_on_selection; + if (focusWasOn) { + focus_on_selection = false; + scale /= 1.2; + } else { + scale /= 2; + calcLeftTop(); + } + redraw(); + focus_on_selection = focusWasOn; + break; + case '=': + case '+': + focusWasOn = focus_on_selection; + if (focusWasOn) { + focus_on_selection = false; + scale *= 1.2; + } else { + scale *= 2; + calcLeftTop(); + } + redraw(); + focus_on_selection = focusWasOn; + break; + case '?': + draw_hints ^= true; + if (draw_hints && !draw_legend) { + draw_legend = true; + } + redraw(); + break; + case '/': + draw_legend ^= true; + redraw(); + break; + } +} + +function doKeyDown(evt) { + var char = evt.keyCode; + var preventDefault = false; + switch (char) { + case 37: // left arrow + if (evt.shiftKey) { + testIndex -= 9; + } + if (--testIndex < 0) + testIndex = tests.length - 1; + drawTop(); + preventDefault = true; + break; + case 39: // right arrow + if (evt.shiftKey) { + testIndex += 9; + } + if (++testIndex >= tests.length) + testIndex = 0; + drawTop(); + preventDefault = true; + break; + } + if (preventDefault) { + evt.preventDefault(); + return false; + } + return true; +} + +(function() { + var hidden = "hidden"; + + // Standards: + if (hidden in document) + document.addEventListener("visibilitychange", onchange); + else if ((hidden = "mozHidden") in document) + document.addEventListener("mozvisibilitychange", onchange); + else if ((hidden = "webkitHidden") in document) + document.addEventListener("webkitvisibilitychange", onchange); + else if ((hidden = "msHidden") in document) + document.addEventListener("msvisibilitychange", onchange); + // IE 9 and lower: + else if ('onfocusin' in document) + document.onfocusin = document.onfocusout = onchange; + // All others: + else + window.onpageshow = window.onpagehide + = window.onfocus = window.onblur = onchange; + + function onchange (evt) { + var v = 'visible', h = 'hidden', + evtMap = { + focus:v, focusin:v, pageshow:v, blur:h, focusout:h, pagehide:h + }; + + evt = evt || window.event; + if (evt.type in evtMap) + document.body.className = evtMap[evt.type]; + else + document.body.className = this[hidden] ? "hidden" : "visible"; + } +})(); + +function calcXY() { + var e = window.event; + var tgt = e.target || e.srcElement; + var left = tgt.offsetLeft; + var top = tgt.offsetTop; + mouseX = (e.clientX - left) / scale + srcLeft; + mouseY = (e.clientY - top) / scale + srcTop; +} + +function calcLeftTop() { + srcLeft = mouseX - screenWidth / 2 / scale; + srcTop = mouseY - screenHeight / 2 / scale; +} + +var disableClick = false; + +function handleMouseClick() { + if (disableClick) { + return; + } + if (!curve_t || !ptInTControl()) { + calcXY(); + calcLeftTop(); + } + redraw(); +// if (!curve_t || !ptInTControl()) { +// mouseX = screenWidth / 2 / scale + srcLeft; +// mouseY = screenHeight / 2 / scale + srcTop; +// } +} + +function handleMouseOver() { + calcXY(); + if (debug_xy != 2) { + return; + } + var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places); + ctx.beginPath(); + ctx.rect(300,100,num.length * 6,10); + ctx.fillStyle="white"; + ctx.fill(); + ctx.font = "normal 10px Arial"; + ctx.fillStyle="black"; + ctx.textAlign = "left"; + ctx.fillText(num, 300, 108); +} + +function start() { + for (var i = 0; i < testDivs.length; ++i) { + tests[i] = null; + } + testIndex = 0; + drawTop(); + window.addEventListener('keypress', doKeyPress, true); + window.addEventListener('keydown', doKeyDown, true); + window.onresize = function() { + drawTop(); + } + /* + window.onpagehide = function() { + disableClick = true; + } + */ + window.onpageshow = function () { + disableClick = false; + } +} + +</script> +</head> + +<body onLoad="start();"> +<canvas id="canvas" width="750" height="500" + onmousemove="handleMouseOver()" + onclick="handleMouseClick()" + ></canvas > +</body> +</html> diff --git a/chromium/third_party/skia/tools/picture_utils.cpp b/chromium/third_party/skia/tools/picture_utils.cpp new file mode 100644 index 00000000000..597fd4878f3 --- /dev/null +++ b/chromium/third_party/skia/tools/picture_utils.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "picture_utils.h" +#include "SkBitmap.h" +#include "SkColorPriv.h" +#include "SkImageEncoder.h" +#include "SkOSFile.h" +#include "SkPicture.h" +#include "SkStream.h" +#include "SkString.h" + +namespace sk_tools { + void force_all_opaque(const SkBitmap& bitmap) { + SkASSERT(NULL == bitmap.getTexture()); + SkASSERT(kN32_SkColorType == bitmap.colorType()); + if (NULL != bitmap.getTexture() || kN32_SkColorType == bitmap.colorType()) { + return; + } + + SkAutoLockPixels lock(bitmap); + for (int y = 0; y < bitmap.height(); y++) { + for (int x = 0; x < bitmap.width(); x++) { + *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT); + } + } + } + + void replace_char(SkString* str, const char oldChar, const char newChar) { + if (NULL == str) { + return; + } + for (size_t i = 0; i < str->size(); ++i) { + if (oldChar == str->operator[](i)) { + str->operator[](i) = newChar; + } + } + } + + bool is_percentage(const char* const string) { + SkString skString(string); + return skString.endsWith("%"); + } + + void setup_bitmap(SkBitmap* bitmap, int width, int height) { + bitmap->allocN32Pixels(width, height); + bitmap->eraseColor(SK_ColorTRANSPARENT); + } + + bool write_bitmap_to_disk(const SkBitmap& bm, const SkString& dirPath, + const char *subdirOrNull, const SkString& baseName) { + SkString partialPath; + if (subdirOrNull) { + partialPath = SkOSPath::SkPathJoin(dirPath.c_str(), subdirOrNull); + sk_mkdir(partialPath.c_str()); + } else { + partialPath.set(dirPath); + } + SkString fullPath = SkOSPath::SkPathJoin(partialPath.c_str(), baseName.c_str()); + if (SkImageEncoder::EncodeFile(fullPath.c_str(), bm, SkImageEncoder::kPNG_Type, 100)) { + return true; + } else { + SkDebugf("Failed to write the bitmap to %s.\n", fullPath.c_str()); + return false; + } + } + +} // namespace sk_tools diff --git a/chromium/third_party/skia/tools/picture_utils.h b/chromium/third_party/skia/tools/picture_utils.h new file mode 100644 index 00000000000..a5fcf7c9689 --- /dev/null +++ b/chromium/third_party/skia/tools/picture_utils.h @@ -0,0 +1,56 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef picture_utils_DEFINED +#define picture_utils_DEFINED + +class SkBitmap; +class SkString; + +namespace sk_tools { + // since PNG insists on unpremultiplying our alpha, we take no precision + // chances and force all pixels to be 100% opaque, otherwise on compare we + // may not get a perfect match. + // + // This expects a bitmap with a config type of 8888 and for the pixels to + // not be on the GPU. + void force_all_opaque(const SkBitmap& bitmap); + + /** + * Replaces all instances of oldChar with newChar in str. + * + * TODO: This function appears here and in skimage_main.cpp ; + * we should add the implementation to src/core/SkString.cpp, write tests for it, + * and remove it from elsewhere. + */ + void replace_char(SkString* str, const char oldChar, const char newChar); + + // Returns true if the string ends with % + bool is_percentage(const char* const string); + + // Prepares the bitmap so that it can be written. + // + // Specifically, it configures the bitmap, allocates pixels and then + // erases the pixels to transparent black. + void setup_bitmap(SkBitmap* bitmap, int width, int height); + + /** + * Write a bitmap file to disk. + * + * @param bm the bitmap to record + * @param dirPath directory within which to write the image file + * @param subdirOrNull subdirectory within dirPath, or NULL to just write into dirPath + * @param baseName last part of the filename + * + * @return true if written out successfully + */ + bool write_bitmap_to_disk(const SkBitmap& bm, const SkString& dirPath, + const char *subdirOrNull, const SkString& baseName); + +} // namespace sk_tools + +#endif // picture_utils_DEFINED diff --git a/chromium/third_party/skia/tools/pinspect.cpp b/chromium/third_party/skia/tools/pinspect.cpp new file mode 100644 index 00000000000..368d6feca7a --- /dev/null +++ b/chromium/third_party/skia/tools/pinspect.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "LazyDecodeBitmap.h" +#include "SkBitmap.h" +#include "SkCanvas.h" +#include "SkGraphics.h" +#include "SkOSFile.h" +#include "SkImageDecoder.h" +#include "SkPicture.h" +#include "SkStream.h" +#include "SkString.h" +#include "SkDumpCanvas.h" + +static SkPicture* inspect(const char path[]) { + SkFILEStream stream(path); + if (!stream.isValid()) { + printf("-- Can't open '%s'\n", path); + return NULL; + } + + printf("Opening '%s'...\n", path); + + { + int32_t header[3]; + if (stream.read(header, sizeof(header)) != sizeof(header)) { + printf("-- Failed to read header (12 bytes)\n"); + return NULL; + } + printf("version:%d width:%d height:%d\n", header[0], header[1], header[2]); + } + + stream.rewind(); + SkPicture* pic = SkPicture::CreateFromStream(&stream, &sk_tools::LazyDecodeBitmap); + if (NULL == pic) { + SkDebugf("Could not create SkPicture: %s\n", path); + return NULL; + } + printf("picture size:[%d %d]\n", pic->width(), pic->height()); + return pic; +} + +static void dumpOps(SkPicture* pic) { +#ifdef SK_DEVELOPER + SkDebugfDumper dumper; + SkDumpCanvas canvas(&dumper); + canvas.drawPicture(pic); +#else + printf("SK_DEVELOPER mode not enabled\n"); +#endif +} + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + SkAutoGraphics ag; + if (argc < 2) { + printf("Usage: pinspect [--dump-ops] filename [filename ...]\n"); + return 1; + } + + bool doDumpOps = false; + + int index = 1; + if (!strcmp(argv[index], "--dump-ops")) { + index += 1; + doDumpOps = true; + } + + for (; index < argc; ++index) { + SkAutoTUnref<SkPicture> pic(inspect(argv[index])); + if (doDumpOps) { + dumpOps(pic); + } + if (index < argc - 1) { + printf("\n"); + } + } + return 0; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/pyutils/__init__.py b/chromium/third_party/skia/tools/pyutils/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/chromium/third_party/skia/tools/pyutils/__init__.py diff --git a/chromium/third_party/skia/tools/pyutils/gs_utils.py b/chromium/third_party/skia/tools/pyutils/gs_utils.py new file mode 100755 index 00000000000..2659c03e631 --- /dev/null +++ b/chromium/third_party/skia/tools/pyutils/gs_utils.py @@ -0,0 +1,81 @@ +#!/usr/bin/python + +""" +Copyright 2014 Google Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. + +Utilities for accessing Google Cloud Storage. + +TODO(epoger): move this into tools/utils for broader use? +""" + +# System-level imports +import os +import posixpath +import sys +try: + from apiclient.discovery import build as build_service +except ImportError: + print ('Missing google-api-python-client. Please install it; directions ' + 'can be found at https://developers.google.com/api-client-library/' + 'python/start/installation') + raise + +# Local imports +import url_utils + + +def download_file(source_bucket, source_path, dest_path, + create_subdirs_if_needed=False): + """ Downloads a single file from Google Cloud Storage to local disk. + + Args: + source_bucket: GCS bucket to download the file from + source_path: full path (Posix-style) within that bucket + dest_path: full path (local-OS-style) on local disk to copy the file to + create_subdirs_if_needed: boolean; whether to create subdirectories as + needed to create dest_path + """ + source_http_url = posixpath.join( + 'http://storage.googleapis.com', source_bucket, source_path) + url_utils.copy_contents(source_url=source_http_url, dest_path=dest_path, + create_subdirs_if_needed=create_subdirs_if_needed) + + +def list_bucket_contents(bucket, subdir=None): + """ Returns files in the Google Cloud Storage bucket as a (dirs, files) tuple. + + Uses the API documented at + https://developers.google.com/storage/docs/json_api/v1/objects/list + + Args: + bucket: name of the Google Storage bucket + subdir: directory within the bucket to list, or None for root directory + """ + # The GCS command relies on the subdir name (if any) ending with a slash. + if subdir and not subdir.endswith('/'): + subdir += '/' + subdir_length = len(subdir) if subdir else 0 + + storage = build_service('storage', 'v1') + command = storage.objects().list( + bucket=bucket, delimiter='/', fields='items(name),prefixes', + prefix=subdir) + results = command.execute() + + # The GCS command returned two subdicts: + # prefixes: the full path of every directory within subdir, with trailing '/' + # items: property dict for each file object within subdir + # (including 'name', which is full path of the object) + dirs = [] + for dir_fullpath in results.get('prefixes', []): + dir_basename = dir_fullpath[subdir_length:] + dirs.append(dir_basename[:-1]) # strip trailing slash + files = [] + for file_properties in results.get('items', []): + file_fullpath = file_properties['name'] + file_basename = file_fullpath[subdir_length:] + files.append(file_basename) + return (dirs, files) diff --git a/chromium/third_party/skia/tools/pyutils/url_utils.py b/chromium/third_party/skia/tools/pyutils/url_utils.py new file mode 100755 index 00000000000..b107f560db7 --- /dev/null +++ b/chromium/third_party/skia/tools/pyutils/url_utils.py @@ -0,0 +1,63 @@ +#!/usr/bin/python + +""" +Copyright 2014 Google Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. + +Utilities for working with URLs. + +TODO(epoger): move this into tools/utils for broader use? +""" + +# System-level imports +import contextlib +import os +import shutil +import urllib +import urlparse + + +def create_filepath_url(filepath): + """ Returns a file:/// URL pointing at the given filepath on local disk. + + Args: + filepath: string; path to a file on local disk (may be absolute or relative, + and the file does not need to exist) + + Returns: + A file:/// URL pointing at the file. Regardless of whether filepath was + specified as a relative or absolute path, the URL will contain an + absolute path to the file. + + Raises: + An Exception, if filepath is already a URL. + """ + if urlparse.urlparse(filepath).scheme: + raise Exception('"%s" is already a URL' % filepath) + return urlparse.urljoin( + 'file:', urllib.pathname2url(os.path.abspath(filepath))) + + +def copy_contents(source_url, dest_path, create_subdirs_if_needed=False): + """ Copies the full contents of the URL 'source_url' into + filepath 'dest_path'. + + Args: + source_url: string; complete URL to read from + dest_path: string; complete filepath to write to (may be absolute or + relative) + create_subdirs_if_needed: boolean; whether to create subdirectories as + needed to create dest_path + + Raises: + Some subclass of Exception if unable to read source_url or write dest_path. + """ + if create_subdirs_if_needed: + dest_dir = os.path.dirname(dest_path) + if not os.path.exists(dest_dir): + os.makedirs(dest_dir) + with contextlib.closing(urllib.urlopen(source_url)) as source_handle: + with open(dest_path, 'wb') as dest_handle: + shutil.copyfileobj(fsrc=source_handle, fdst=dest_handle) diff --git a/chromium/third_party/skia/tools/pyutils/url_utils_test.py b/chromium/third_party/skia/tools/pyutils/url_utils_test.py new file mode 100755 index 00000000000..ef3d8c8aaaa --- /dev/null +++ b/chromium/third_party/skia/tools/pyutils/url_utils_test.py @@ -0,0 +1,61 @@ +#!/usr/bin/python + +""" +Copyright 2014 Google Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. + +Test url_utils.py +""" + +# System-level imports +import os +import shutil +import tempfile +import unittest +import urllib + +# Imports from within Skia +import url_utils + + +class UrlUtilsTest(unittest.TestCase): + + def test_create_filepath_url(self): + """Tests create_filepath_url(). """ + with self.assertRaises(Exception): + url_utils.create_filepath_url('http://1.2.3.4/path') + # Pass absolute filepath. + self.assertEquals( + url_utils.create_filepath_url( + '%sdir%sfile' % (os.path.sep, os.path.sep)), + 'file:///dir/file') + # Pass relative filepath. + self.assertEquals( + url_utils.create_filepath_url(os.path.join('dir', 'file')), + 'file://%s/dir/file' % urllib.pathname2url(os.getcwd())) + + def test_copy_contents(self): + """Tests copy_contents(). """ + contents = 'these are the contents' + tempdir_path = tempfile.mkdtemp() + try: + source_path = os.path.join(tempdir_path, 'source') + source_url = url_utils.create_filepath_url(source_path) + with open(source_path, 'w') as source_handle: + source_handle.write(contents) + dest_path = os.path.join(tempdir_path, 'new_subdir', 'dest') + # Destination subdir does not exist, so copy_contents() should fail + # if create_subdirs_if_needed is False. + with self.assertRaises(Exception): + url_utils.copy_contents(source_url=source_url, + dest_path=dest_path, + create_subdirs_if_needed=False) + # If create_subdirs_if_needed is True, it should work. + url_utils.copy_contents(source_url=source_url, + dest_path=dest_path, + create_subdirs_if_needed=True) + self.assertEquals(open(dest_path).read(), contents) + finally: + shutil.rmtree(tempdir_path) diff --git a/chromium/third_party/skia/tools/reformat-json.py b/chromium/third_party/skia/tools/reformat-json.py new file mode 100755 index 00000000000..593d6226154 --- /dev/null +++ b/chromium/third_party/skia/tools/reformat-json.py @@ -0,0 +1,55 @@ +#!/usr/bin/python + +''' +Copyright 2013 Google Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +''' + +''' +Rewrites a JSON file to use Python's standard JSON pretty-print format, +so that subsequent runs of rebaseline.py will generate useful diffs +(only the actual checksum differences will show up as diffs, not obscured +by format differences). + +Should not modify the JSON contents in any meaningful way. +''' + +# System-level imports +import argparse +import os +import sys + +# Imports from within Skia +# +# We need to add the 'gm' directory, so that we can import gm_json.py within +# that directory. That script allows us to parse the actual-results.json file +# written out by the GM tool. +# Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end* +# so any dirs that are already in the PYTHONPATH will be preferred. +# +# This assumes that the 'gm' directory has been checked out as a sibling of +# the 'tools' directory containing this script, which will be the case if +# 'trunk' was checked out as a single unit. +GM_DIRECTORY = os.path.realpath( + os.path.join(os.path.dirname(os.path.dirname(__file__)), 'gm')) +if GM_DIRECTORY not in sys.path: + sys.path.append(GM_DIRECTORY) +import gm_json + +def Reformat(filename): + print 'Reformatting file %s...' % filename + gm_json.WriteToFile(gm_json.LoadFromFile(filename), filename) + +def _Main(): + parser = argparse.ArgumentParser(description='Reformat JSON files in-place.') + parser.add_argument('filenames', metavar='FILENAME', nargs='+', + help='file to reformat') + args = parser.parse_args() + for filename in args.filenames: + Reformat(filename) + sys.exit(0) + +if __name__ == '__main__': + _Main() diff --git a/chromium/third_party/skia/tools/render_pdfs_main.cpp b/chromium/third_party/skia/tools/render_pdfs_main.cpp new file mode 100644 index 00000000000..5e337009c6c --- /dev/null +++ b/chromium/third_party/skia/tools/render_pdfs_main.cpp @@ -0,0 +1,302 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkCanvas.h" +#include "SkDevice.h" +#include "SkForceLinking.h" +#include "SkGraphics.h" +#include "SkImageEncoder.h" +#include "SkOSFile.h" +#include "SkPicture.h" +#include "SkPixelRef.h" +#include "SkStream.h" +#include "SkTArray.h" +#include "PdfRenderer.h" +#include "picture_utils.h" + +__SK_FORCE_IMAGE_DECODER_LINKING; + +#ifdef SK_USE_CDB +#include "win_dbghelp.h" +#endif + +/** + * render_pdfs + * + * Given list of directories and files to use as input, expects to find .skp + * files and it will convert them to .pdf files writing them in the output + * directory. + * + * Returns zero exit code if all .skp files were converted successfully, + * otherwise returns error code 1. + */ + +static const char PDF_FILE_EXTENSION[] = "pdf"; +static const char SKP_FILE_EXTENSION[] = "skp"; + +static void usage(const char* argv0) { + SkDebugf("SKP to PDF rendering tool\n"); + SkDebugf("\n" +"Usage: \n" +" %s <input>... [-w <outputDir>] [--jpegQuality N] \n" +, argv0); + SkDebugf("\n\n"); + SkDebugf( +" input: A list of directories and files to use as input. Files are\n" +" expected to have the .skp extension.\n\n"); + SkDebugf( +" outputDir: directory to write the rendered pdfs.\n\n"); + SkDebugf("\n"); + SkDebugf( +" jpegQuality N: encodes images in JPEG at quality level N, which can\n" +" be in range 0-100).\n" +" N = -1 will disable JPEG compression.\n" +" Default is N = 100, maximum quality.\n\n"); + SkDebugf("\n"); +} + +/** Replaces the extension of a file. + * @param path File name whose extension will be changed. + * @param old_extension The old extension. + * @param new_extension The new extension. + * @returns false if the file did not has the expected extension. + * if false is returned, contents of path are undefined. + */ +static bool replace_filename_extension(SkString* path, + const char old_extension[], + const char new_extension[]) { + if (path->endsWith(old_extension)) { + path->remove(path->size() - strlen(old_extension), + strlen(old_extension)); + if (!path->endsWith(".")) { + return false; + } + path->append(new_extension); + return true; + } + return false; +} + +int gJpegQuality = 100; +// the size_t* parameter is deprecated, so we ignore it +static SkData* encode_to_dct_data(size_t*, const SkBitmap& bitmap) { + if (gJpegQuality == -1) { + return NULL; + } + + SkBitmap bm = bitmap; +#if defined(SK_BUILD_FOR_MAC) + // Workaround bug #1043 where bitmaps with referenced pixels cause + // CGImageDestinationFinalize to crash + SkBitmap copy; + bitmap.deepCopyTo(©); + bm = copy; +#endif + + return SkImageEncoder::EncodeData(bm, + SkImageEncoder::kJPEG_Type, + gJpegQuality); +} + +/** Builds the output filename. path = dir/name, and it replaces expected + * .skp extension with .pdf extention. + * @param path Output filename. + * @param name The name of the file. + * @returns false if the file did not has the expected extension. + * if false is returned, contents of path are undefined. + */ +static bool make_output_filepath(SkString* path, const SkString& dir, + const SkString& name) { + *path = SkOSPath::SkPathJoin(dir.c_str(), name.c_str()); + return replace_filename_extension(path, + SKP_FILE_EXTENSION, + PDF_FILE_EXTENSION); +} + +/** Write the output of pdf renderer to a file. + * @param outputDir Output dir. + * @param inputFilename The skp file that was read. + * @param renderer The object responsible to write the pdf file. + */ +static SkWStream* open_stream(const SkString& outputDir, + const SkString& inputFilename) { + if (outputDir.isEmpty()) { + return SkNEW(SkDynamicMemoryWStream); + } + + SkString outputPath; + if (!make_output_filepath(&outputPath, outputDir, inputFilename)) { + return NULL; + } + + SkFILEWStream* stream = SkNEW_ARGS(SkFILEWStream, (outputPath.c_str())); + if (!stream->isValid()) { + SkDebugf("Could not write to file %s\n", outputPath.c_str()); + return NULL; + } + + return stream; +} + +/** Reads an skp file, renders it to pdf and writes the output to a pdf file + * @param inputPath The skp file to be read. + * @param outputDir Output dir. + * @param renderer The object responsible to render the skp object into pdf. + */ +static bool render_pdf(const SkString& inputPath, const SkString& outputDir, + sk_tools::PdfRenderer& renderer) { + SkString inputFilename = SkOSPath::SkBasename(inputPath.c_str()); + + SkFILEStream inputStream; + inputStream.setPath(inputPath.c_str()); + if (!inputStream.isValid()) { + SkDebugf("Could not open file %s\n", inputPath.c_str()); + return false; + } + + SkAutoTUnref<SkPicture> picture(SkPicture::CreateFromStream(&inputStream)); + + if (NULL == picture.get()) { + SkDebugf("Could not read an SkPicture from %s\n", inputPath.c_str()); + return false; + } + + SkDebugf("exporting... [%i %i] %s\n", picture->width(), picture->height(), + inputPath.c_str()); + + SkWStream* stream(open_stream(outputDir, inputFilename)); + + if (!stream) { + return false; + } + + renderer.init(picture, stream); + + bool success = renderer.render(); + SkDELETE(stream); + + renderer.end(); + + return success; +} + +/** For each file in the directory or for the file passed in input, call + * render_pdf. + * @param input A directory or an skp file. + * @param outputDir Output dir. + * @param renderer The object responsible to render the skp object into pdf. + */ +static int process_input(const SkString& input, const SkString& outputDir, + sk_tools::PdfRenderer& renderer) { + int failures = 0; + if (sk_isdir(input.c_str())) { + SkOSFile::Iter iter(input.c_str(), SKP_FILE_EXTENSION); + SkString inputFilename; + while (iter.next(&inputFilename)) { + SkString inputPath = SkOSPath::SkPathJoin(input.c_str(), inputFilename.c_str()); + if (!render_pdf(inputPath, outputDir, renderer)) { + ++failures; + } + } + } else { + SkString inputPath(input); + if (!render_pdf(inputPath, outputDir, renderer)) { + ++failures; + } + } + return failures; +} + +static void parse_commandline(int argc, char* const argv[], + SkTArray<SkString>* inputs, + SkString* outputDir) { + const char* argv0 = argv[0]; + char* const* stop = argv + argc; + + for (++argv; argv < stop; ++argv) { + if ((0 == strcmp(*argv, "-h")) || (0 == strcmp(*argv, "--help"))) { + usage(argv0); + exit(-1); + } else if (0 == strcmp(*argv, "-w")) { + ++argv; + if (argv >= stop) { + SkDebugf("Missing outputDir for -w\n"); + usage(argv0); + exit(-1); + } + *outputDir = SkString(*argv); + } else if (0 == strcmp(*argv, "--jpegQuality")) { + ++argv; + if (argv >= stop) { + SkDebugf("Missing argument for --jpegQuality\n"); + usage(argv0); + exit(-1); + } + gJpegQuality = atoi(*argv); + if (gJpegQuality < -1 || gJpegQuality > 100) { + SkDebugf("Invalid argument for --jpegQuality\n"); + usage(argv0); + exit(-1); + } + } else { + inputs->push_back(SkString(*argv)); + } + } + + if (inputs->count() < 1) { + usage(argv0); + exit(-1); + } +} + +int tool_main_core(int argc, char** argv); +int tool_main_core(int argc, char** argv) { + SkAutoGraphics ag; + SkTArray<SkString> inputs; + + SkAutoTUnref<sk_tools::PdfRenderer> + renderer(SkNEW_ARGS(sk_tools::SimplePdfRenderer, (encode_to_dct_data))); + SkASSERT(renderer.get()); + + SkString outputDir; + parse_commandline(argc, argv, &inputs, &outputDir); + + int failures = 0; + for (int i = 0; i < inputs.count(); i ++) { + failures += process_input(inputs[i], outputDir, *renderer); + } + + if (failures != 0) { + SkDebugf("Failed to render %i PDFs.\n", failures); + return 1; + } + + return 0; +} + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { +#ifdef SK_USE_CDB + setUpDebuggingFromArgs(argv[0]); + __try { +#endif + return tool_main_core(argc, argv); +#ifdef SK_USE_CDB + } + __except(GenerateDumpAndPrintCallstack(GetExceptionInformation())) + { + return -1; + } +#endif + return 0; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/render_pictures_main.cpp b/chromium/third_party/skia/tools/render_pictures_main.cpp new file mode 100644 index 00000000000..850053cfc6f --- /dev/null +++ b/chromium/third_party/skia/tools/render_pictures_main.cpp @@ -0,0 +1,503 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "LazyDecodeBitmap.h" +#include "CopyTilesRenderer.h" +#include "SkBitmap.h" +#include "SkDevice.h" +#include "SkCommandLineFlags.h" +#include "SkGraphics.h" +#include "SkImageDecoder.h" +#include "SkImageEncoder.h" +#include "SkMath.h" +#include "SkOSFile.h" +#include "SkPicture.h" +#include "SkPictureRecorder.h" +#include "SkStream.h" +#include "SkString.h" + +#include "image_expectations.h" +#include "PictureRenderer.h" +#include "PictureRenderingFlags.h" +#include "picture_utils.h" + +// Flags used by this file, alphabetically: +DEFINE_int32(clone, 0, "Clone the picture n times before rendering."); +DECLARE_bool(deferImageDecoding); +DEFINE_int32(maxComponentDiff, 256, "Maximum diff on a component, 0 - 256. Components that differ " + "by more than this amount are considered errors, though all diffs are reported. " + "Requires --validate."); +DEFINE_string(mismatchPath, "", "Write images for tests that failed due to " + "pixel mismatches into this directory."); +DEFINE_string(readJsonSummaryPath, "", "JSON file to read image expectations from."); +DECLARE_string(readPath); +DEFINE_bool(writeChecksumBasedFilenames, false, + "When writing out images, use checksum-based filenames."); +DEFINE_bool(writeEncodedImages, false, "Any time the skp contains an encoded image, write it to a " + "file rather than decoding it. Requires writePath to be set. Skips drawing the full " + "skp to a file. Not compatible with deferImageDecoding."); +DEFINE_string(writeJsonSummaryPath, "", "File to write a JSON summary of image results to."); +DEFINE_string2(writePath, w, "", "Directory to write the rendered images into."); +DEFINE_bool(writeWholeImage, false, "In tile mode, write the entire rendered image to a " + "file, instead of an image for each tile."); +DEFINE_bool(validate, false, "Verify that the rendered image contains the same pixels as " + "the picture rendered in simple mode. When used in conjunction with --bbh, results " + "are validated against the picture rendered in the same mode, but without the bbh."); + +DEFINE_bool(bench_record, false, "If true, drop into an infinite loop of recording the picture."); + +DEFINE_bool(preprocess, false, "If true, perform device specific preprocessing before rendering."); + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Table for translating from format of data to a suffix. + */ +struct Format { + SkImageDecoder::Format fFormat; + const char* fSuffix; +}; +static const Format gFormats[] = { + { SkImageDecoder::kBMP_Format, ".bmp" }, + { SkImageDecoder::kGIF_Format, ".gif" }, + { SkImageDecoder::kICO_Format, ".ico" }, + { SkImageDecoder::kJPEG_Format, ".jpg" }, + { SkImageDecoder::kPNG_Format, ".png" }, + { SkImageDecoder::kWBMP_Format, ".wbmp" }, + { SkImageDecoder::kWEBP_Format, ".webp" }, + { SkImageDecoder::kUnknown_Format, "" }, +}; + +/** + * Get an appropriate suffix for an image format. + */ +static const char* get_suffix_from_format(SkImageDecoder::Format format) { + for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) { + if (gFormats[i].fFormat == format) { + return gFormats[i].fSuffix; + } + } + return ""; +} + +/** + * Base name for an image file created from the encoded data in an skp. + */ +static SkString gInputFileName; + +/** + * Number to be appended to the image file name so that it is unique. + */ +static uint32_t gImageNo; + +/** + * Set up the name for writing encoded data to a file. + * Sets gInputFileName to name, minus any extension ".*" + * Sets gImageNo to 0, so images from file "X.skp" will + * look like "X_<gImageNo>.<suffix>", beginning with 0 + * for each new skp. + */ +static void reset_image_file_base_name(const SkString& name) { + gImageNo = 0; + // Remove ".skp" + const char* cName = name.c_str(); + const char* dot = strrchr(cName, '.'); + if (dot != NULL) { + gInputFileName.set(cName, dot - cName); + } else { + gInputFileName.set(name); + } +} + +/** + * Write the raw encoded bitmap data to a file. + */ +static bool write_image_to_file(const void* buffer, size_t size, SkBitmap* bitmap) { + SkASSERT(!FLAGS_writePath.isEmpty()); + SkMemoryStream memStream(buffer, size); + SkString outPath; + SkImageDecoder::Format format = SkImageDecoder::GetStreamFormat(&memStream); + SkString name = SkStringPrintf("%s_%d%s", gInputFileName.c_str(), gImageNo++, + get_suffix_from_format(format)); + SkString dir(FLAGS_writePath[0]); + outPath = SkOSPath::SkPathJoin(dir.c_str(), name.c_str()); + SkFILEWStream fileStream(outPath.c_str()); + if (!(fileStream.isValid() && fileStream.write(buffer, size))) { + SkDebugf("Failed to write encoded data to \"%s\"\n", outPath.c_str()); + } + // Put in a dummy bitmap. + return SkImageDecoder::DecodeStream(&memStream, bitmap, kUnknown_SkColorType, + SkImageDecoder::kDecodeBounds_Mode); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Called only by render_picture(). + */ +static bool render_picture_internal(const SkString& inputPath, const SkString* writePath, + const SkString* mismatchPath, + sk_tools::PictureRenderer& renderer, + SkBitmap** out) { + SkString inputFilename = SkOSPath::SkBasename(inputPath.c_str()); + SkString writePathString; + if (NULL != writePath && writePath->size() > 0 && !FLAGS_writeEncodedImages) { + writePathString.set(*writePath); + } + SkString mismatchPathString; + if (NULL != mismatchPath && mismatchPath->size() > 0) { + mismatchPathString.set(*mismatchPath); + } + + SkFILEStream inputStream; + inputStream.setPath(inputPath.c_str()); + if (!inputStream.isValid()) { + SkDebugf("Could not open file %s\n", inputPath.c_str()); + return false; + } + + SkPicture::InstallPixelRefProc proc; + if (FLAGS_deferImageDecoding) { + proc = &sk_tools::LazyDecodeBitmap; + } else if (FLAGS_writeEncodedImages) { + SkASSERT(!FLAGS_writePath.isEmpty()); + reset_image_file_base_name(inputFilename); + proc = &write_image_to_file; + } else { + proc = &SkImageDecoder::DecodeMemory; + } + + SkDebugf("deserializing... %s\n", inputPath.c_str()); + + SkPicture* picture = SkPicture::CreateFromStream(&inputStream, proc); + + if (NULL == picture) { + SkDebugf("Could not read an SkPicture from %s\n", inputPath.c_str()); + return false; + } + + while (FLAGS_bench_record) { + SkPictureRecorder recorder; + picture->draw(recorder.beginRecording(picture->width(), picture->height(), NULL, 0)); + SkAutoTUnref<SkPicture> other(recorder.endRecording()); + } + + for (int i = 0; i < FLAGS_clone; ++i) { + SkPicture* clone = picture->clone(); + SkDELETE(picture); + picture = clone; + } + + SkDebugf("drawing... [%i %i] %s\n", picture->width(), picture->height(), + inputPath.c_str()); + + renderer.init(picture, &writePathString, &mismatchPathString, &inputFilename, + FLAGS_writeChecksumBasedFilenames); + + if (FLAGS_preprocess) { + if (NULL != renderer.getCanvas()) { + renderer.getCanvas()->EXPERIMENTAL_optimize(renderer.getPicture()); + } + } + + renderer.setup(); + renderer.enableWrites(); + + bool success = renderer.render(out); + if (!success) { + SkDebugf("Failed to render %s\n", inputFilename.c_str()); + } + + renderer.end(); + + SkDELETE(picture); + return success; +} + +static inline int getByte(uint32_t value, int index) { + SkASSERT(0 <= index && index < 4); + return (value >> (index * 8)) & 0xFF; +} + +static int MaxByteDiff(uint32_t v1, uint32_t v2) { + return SkMax32(SkMax32(abs(getByte(v1, 0) - getByte(v2, 0)), abs(getByte(v1, 1) - getByte(v2, 1))), + SkMax32(abs(getByte(v1, 2) - getByte(v2, 2)), abs(getByte(v1, 3) - getByte(v2, 3)))); +} + +class AutoRestoreBbhType { +public: + AutoRestoreBbhType() { + fRenderer = NULL; + fSavedBbhType = sk_tools::PictureRenderer::kNone_BBoxHierarchyType; + } + + void set(sk_tools::PictureRenderer* renderer, + sk_tools::PictureRenderer::BBoxHierarchyType bbhType) { + fRenderer = renderer; + fSavedBbhType = renderer->getBBoxHierarchyType(); + renderer->setBBoxHierarchyType(bbhType); + } + + ~AutoRestoreBbhType() { + if (NULL != fRenderer) { + fRenderer->setBBoxHierarchyType(fSavedBbhType); + } + } + +private: + sk_tools::PictureRenderer* fRenderer; + sk_tools::PictureRenderer::BBoxHierarchyType fSavedBbhType; +}; + +/** + * Render the SKP file(s) within inputPath. + * + * @param inputPath path to an individual SKP file, or a directory of SKP files + * @param writePath if not NULL, write all image(s) generated into this directory + * @param mismatchPath if not NULL, write any image(s) not matching expectations into this directory + * @param renderer PictureRenderer to use to render the SKPs + * @param jsonSummaryPtr if not NULL, add the image(s) generated to this summary + */ +static bool render_picture(const SkString& inputPath, const SkString* writePath, + const SkString* mismatchPath, sk_tools::PictureRenderer& renderer, + sk_tools::ImageResultsAndExpectations *jsonSummaryPtr) { + int diffs[256] = {0}; + SkBitmap* bitmap = NULL; + renderer.setJsonSummaryPtr(jsonSummaryPtr); + bool success = render_picture_internal(inputPath, + FLAGS_writeWholeImage ? NULL : writePath, + FLAGS_writeWholeImage ? NULL : mismatchPath, + renderer, + FLAGS_validate || FLAGS_writeWholeImage ? &bitmap : NULL); + + if (!success || ((FLAGS_validate || FLAGS_writeWholeImage) && bitmap == NULL)) { + SkDebugf("Failed to draw the picture.\n"); + SkDELETE(bitmap); + return false; + } + + if (FLAGS_validate) { + SkBitmap* referenceBitmap = NULL; + sk_tools::PictureRenderer* referenceRenderer; + // If the renderer uses a BBoxHierarchy, then the reference renderer + // will be the same renderer, without the bbh. + AutoRestoreBbhType arbbh; + if (sk_tools::PictureRenderer::kNone_BBoxHierarchyType != + renderer.getBBoxHierarchyType()) { + referenceRenderer = &renderer; + referenceRenderer->ref(); // to match auto unref below + arbbh.set(referenceRenderer, sk_tools::PictureRenderer::kNone_BBoxHierarchyType); + } else { + referenceRenderer = SkNEW(sk_tools::SimplePictureRenderer); + } + SkAutoTUnref<sk_tools::PictureRenderer> aurReferenceRenderer(referenceRenderer); + + success = render_picture_internal(inputPath, NULL, NULL, *referenceRenderer, + &referenceBitmap); + + if (!success || NULL == referenceBitmap || NULL == referenceBitmap->getPixels()) { + SkDebugf("Failed to draw the reference picture.\n"); + SkDELETE(bitmap); + SkDELETE(referenceBitmap); + return false; + } + + if (success && (bitmap->width() != referenceBitmap->width())) { + SkDebugf("Expected image width: %i, actual image width %i.\n", + referenceBitmap->width(), bitmap->width()); + SkDELETE(bitmap); + SkDELETE(referenceBitmap); + return false; + } + if (success && (bitmap->height() != referenceBitmap->height())) { + SkDebugf("Expected image height: %i, actual image height %i", + referenceBitmap->height(), bitmap->height()); + SkDELETE(bitmap); + SkDELETE(referenceBitmap); + return false; + } + + for (int y = 0; success && y < bitmap->height(); y++) { + for (int x = 0; success && x < bitmap->width(); x++) { + int diff = MaxByteDiff(*referenceBitmap->getAddr32(x, y), + *bitmap->getAddr32(x, y)); + SkASSERT(diff >= 0 && diff <= 255); + diffs[diff]++; + + if (diff > FLAGS_maxComponentDiff) { + SkDebugf("Expected pixel at (%i %i) exceedds maximum " + "component diff of %i: 0x%x, actual 0x%x\n", + x, y, FLAGS_maxComponentDiff, + *referenceBitmap->getAddr32(x, y), + *bitmap->getAddr32(x, y)); + SkDELETE(bitmap); + SkDELETE(referenceBitmap); + return false; + } + } + } + SkDELETE(referenceBitmap); + + for (int i = 1; i <= 255; ++i) { + if(diffs[i] > 0) { + SkDebugf("Number of pixels with max diff of %i is %i\n", i, diffs[i]); + } + } + } + + if (FLAGS_writeWholeImage) { + sk_tools::force_all_opaque(*bitmap); + + SkString inputFilename = SkOSPath::SkBasename(inputPath.c_str()); + SkString outputFilename(inputFilename); + sk_tools::replace_char(&outputFilename, '.', '_'); + outputFilename.append(".png"); + + if (NULL != jsonSummaryPtr) { + sk_tools::ImageDigest imageDigest(*bitmap); + jsonSummaryPtr->add(inputFilename.c_str(), outputFilename.c_str(), imageDigest); + if ((NULL != mismatchPath) && !mismatchPath->isEmpty() && + !jsonSummaryPtr->matchesExpectation(inputFilename.c_str(), imageDigest)) { + success &= sk_tools::write_bitmap_to_disk(*bitmap, *mismatchPath, NULL, + outputFilename); + } + } + + if ((NULL != writePath) && !writePath->isEmpty()) { + success &= sk_tools::write_bitmap_to_disk(*bitmap, *writePath, NULL, outputFilename); + } + } + SkDELETE(bitmap); + + return success; +} + + +static int process_input(const char* input, const SkString* writePath, + const SkString* mismatchPath, sk_tools::PictureRenderer& renderer, + sk_tools::ImageResultsAndExpectations *jsonSummaryPtr) { + SkOSFile::Iter iter(input, "skp"); + SkString inputFilename; + int failures = 0; + SkDebugf("process_input, %s\n", input); + if (iter.next(&inputFilename)) { + do { + SkString inputPath = SkOSPath::SkPathJoin(input, inputFilename.c_str()); + if (!render_picture(inputPath, writePath, mismatchPath, renderer, jsonSummaryPtr)) { + ++failures; + } + } while(iter.next(&inputFilename)); + } else if (SkStrEndsWith(input, ".skp")) { + SkString inputPath(input); + if (!render_picture(inputPath, writePath, mismatchPath, renderer, jsonSummaryPtr)) { + ++failures; + } + } else { + SkString warning; + warning.printf("Warning: skipping %s\n", input); + SkDebugf(warning.c_str()); + } + return failures; +} + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + SkCommandLineFlags::SetUsage("Render .skp files."); + SkCommandLineFlags::Parse(argc, argv); + + if (FLAGS_readPath.isEmpty()) { + SkDebugf(".skp files or directories are required.\n"); + exit(-1); + } + + if (FLAGS_maxComponentDiff < 0 || FLAGS_maxComponentDiff > 256) { + SkDebugf("--maxComponentDiff must be between 0 and 256\n"); + exit(-1); + } + + if (FLAGS_maxComponentDiff != 256 && !FLAGS_validate) { + SkDebugf("--maxComponentDiff requires --validate\n"); + exit(-1); + } + + if (FLAGS_clone < 0) { + SkDebugf("--clone must be >= 0. Was %i\n", FLAGS_clone); + exit(-1); + } + + if (FLAGS_writeEncodedImages) { + if (FLAGS_writePath.isEmpty()) { + SkDebugf("--writeEncodedImages requires --writePath\n"); + exit(-1); + } + if (FLAGS_deferImageDecoding) { + SkDebugf("--writeEncodedImages is not compatible with --deferImageDecoding\n"); + exit(-1); + } + } + + SkString errorString; + SkAutoTUnref<sk_tools::PictureRenderer> renderer(parseRenderer(errorString, + kRender_PictureTool)); + if (errorString.size() > 0) { + SkDebugf("%s\n", errorString.c_str()); + } + + if (renderer.get() == NULL) { + exit(-1); + } + + SkAutoGraphics ag; + + SkString writePath; + if (FLAGS_writePath.count() == 1) { + writePath.set(FLAGS_writePath[0]); + } + SkString mismatchPath; + if (FLAGS_mismatchPath.count() == 1) { + mismatchPath.set(FLAGS_mismatchPath[0]); + } + sk_tools::ImageResultsAndExpectations jsonSummary; + sk_tools::ImageResultsAndExpectations* jsonSummaryPtr = NULL; + if (FLAGS_writeJsonSummaryPath.count() == 1) { + jsonSummaryPtr = &jsonSummary; + if (FLAGS_readJsonSummaryPath.count() == 1) { + SkASSERT(jsonSummary.readExpectationsFile(FLAGS_readJsonSummaryPath[0])); + } + } + + int failures = 0; + for (int i = 0; i < FLAGS_readPath.count(); i ++) { + failures += process_input(FLAGS_readPath[i], &writePath, &mismatchPath, *renderer.get(), + jsonSummaryPtr); + } + if (failures != 0) { + SkDebugf("Failed to render %i pictures.\n", failures); + return 1; + } +#if SK_SUPPORT_GPU +#if GR_CACHE_STATS + if (renderer->isUsingGpuDevice()) { + GrContext* ctx = renderer->getGrContext(); + ctx->printCacheStats(); +#ifdef SK_DEVELOPER + ctx->dumpFontCache(); +#endif + } +#endif +#endif + if (FLAGS_writeJsonSummaryPath.count() == 1) { + jsonSummary.writeToFile(FLAGS_writeJsonSummaryPath[0]); + } + return 0; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/retrieve_from_googlesource.py b/chromium/third_party/skia/tools/retrieve_from_googlesource.py new file mode 100644 index 00000000000..2f78ce03928 --- /dev/null +++ b/chromium/third_party/skia/tools/retrieve_from_googlesource.py @@ -0,0 +1,37 @@ +#!/usr/bin/python + +# Copyright (c) 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +"""Retrieve the given file from googlesource.com.""" + + +from contextlib import closing + +import base64 +import sys +import urllib2 + + +def get(repo_url, filepath): + """Retrieve the contents of the given file from the given googlesource repo. + + Args: + repo_url: string; URL of the repository from which to retrieve the file. + filepath: string; path of the file within the repository. + + Return: + string; the contents of the given file. + """ + base64_url = '/'.join((repo_url, '+', 'master', filepath)) + '?format=TEXT' + with closing(urllib2.urlopen(base64_url)) as f: + return base64.b64decode(f.read()) + + +if __name__ == '__main__': + if len(sys.argv) != 3: + print >> sys.stderr, 'Usage: %s <repo_url> <filepath>' % sys.argv[0] + sys.exit(1) + sys.stdout.write(get(sys.argv[1], sys.argv[2])) diff --git a/chromium/third_party/skia/tools/roll_deps.py b/chromium/third_party/skia/tools/roll_deps.py new file mode 100755 index 00000000000..d280bda11e6 --- /dev/null +++ b/chromium/third_party/skia/tools/roll_deps.py @@ -0,0 +1,543 @@ +#!/usr/bin/python2 + +# Copyright 2014 Google Inc. +# +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Skia's Chromium DEPS roll script. + +This script: +- searches through the last N Skia git commits to find out the hash that is + associated with the SVN revision number. +- creates a new branch in the Chromium tree, modifies the DEPS file to + point at the given Skia commit, commits, uploads to Rietveld, and + deletes the local copy of the branch. +- creates a whitespace-only commit and uploads that to to Rietveld. +- returns the Chromium tree to its previous state. + +To specify the location of the git executable, set the GIT_EXECUTABLE +environment variable. + +Usage: + %prog -c CHROMIUM_PATH -r REVISION [OPTIONAL_OPTIONS] +""" + + +import optparse +import os +import re +import shutil +import subprocess +import sys +import tempfile + +import git_utils +import misc_utils + + +DEFAULT_BOTS_LIST = [ + 'android_clang_dbg', + 'android_dbg', + 'android_rel', + 'cros_daisy', + 'linux', + 'linux_asan', + 'linux_chromeos', + 'linux_chromeos_asan', + 'linux_chromium_gn_dbg', + 'linux_gpu', + 'linux_layout', + 'linux_layout_rel', + 'mac', + 'mac_asan', + 'mac_gpu', + 'mac_layout', + 'mac_layout_rel', + 'win', + 'win_gpu', + 'win_layout', + 'win_layout_rel', +] + + +class DepsRollConfig(object): + """Contains configuration options for this module. + + Attributes: + git: (string) The git executable. + chromium_path: (string) path to a local chromium git repository. + save_branches: (boolean) iff false, delete temporary branches. + verbose: (boolean) iff false, suppress the output from git-cl. + search_depth: (int) how far back to look for the revision. + skia_url: (string) Skia's git repository. + self.skip_cl_upload: (boolean) + self.cl_bot_list: (list of strings) + """ + + # pylint: disable=I0011,R0903,R0902 + def __init__(self, options=None): + self.skia_url = 'https://skia.googlesource.com/skia.git' + self.revision_format = ( + 'git-svn-id: http://skia.googlecode.com/svn/trunk@%d ') + + self.git = git_utils.git_executable() + + if not options: + options = DepsRollConfig.GetOptionParser() + # pylint: disable=I0011,E1103 + self.verbose = options.verbose + self.vsp = misc_utils.VerboseSubprocess(self.verbose) + self.save_branches = not options.delete_branches + self.search_depth = options.search_depth + self.chromium_path = options.chromium_path + self.skip_cl_upload = options.skip_cl_upload + # Split and remove empty strigns from the bot list. + self.cl_bot_list = [bot for bot in options.bots.split(',') if bot] + self.skia_git_checkout_path = options.skia_git_path + self.default_branch_name = 'autogenerated_deps_roll_branch' + self.reviewers_list = ','.join([ + # 'rmistry@google.com', + # 'reed@google.com', + # 'bsalomon@google.com', + # 'robertphillips@google.com', + ]) + self.cc_list = ','.join([ + # 'skia-team@google.com', + ]) + + @staticmethod + def GetOptionParser(): + # pylint: disable=I0011,C0103 + """Returns an optparse.OptionParser object. + + Returns: + An optparse.OptionParser object. + + Called by the main() function. + """ + option_parser = optparse.OptionParser(usage=__doc__) + # Anyone using this script on a regular basis should set the + # CHROMIUM_CHECKOUT_PATH environment variable. + option_parser.add_option( + '-c', '--chromium_path', help='Path to local Chromium Git' + ' repository checkout, defaults to CHROMIUM_CHECKOUT_PATH' + ' if that environment variable is set.', + default=os.environ.get('CHROMIUM_CHECKOUT_PATH')) + option_parser.add_option( + '-r', '--revision', type='int', default=None, + help='The Skia SVN revision number, defaults to top of tree.') + option_parser.add_option( + '-g', '--git_hash', default=None, + help='A partial Skia Git hash. Do not set this and revision.') + + # Anyone using this script on a regular basis should set the + # SKIA_GIT_CHECKOUT_PATH environment variable. + option_parser.add_option( + '', '--skia_git_path', + help='Path of a pure-git Skia repository checkout. If empty,' + ' a temporary will be cloned. Defaults to SKIA_GIT_CHECKOUT' + '_PATH, if that environment variable is set.', + default=os.environ.get('SKIA_GIT_CHECKOUT_PATH')) + option_parser.add_option( + '', '--search_depth', type='int', default=100, + help='How far back to look for the revision.') + option_parser.add_option( + '', '--delete_branches', help='Delete the temporary branches', + action='store_true', dest='delete_branches', default=False) + option_parser.add_option( + '', '--verbose', help='Do not suppress the output from `git cl`.', + action='store_true', dest='verbose', default=False) + option_parser.add_option( + '', '--skip_cl_upload', help='Skip the cl upload step; useful' + ' for testing.', + action='store_true', default=False) + + default_bots_help = ( + 'Comma-separated list of bots, defaults to a list of %d bots.' + ' To skip `git cl try`, set this to an empty string.' + % len(DEFAULT_BOTS_LIST)) + default_bots = ','.join(DEFAULT_BOTS_LIST) + option_parser.add_option( + '', '--bots', help=default_bots_help, default=default_bots) + + return option_parser + + +class DepsRollError(Exception): + """Exceptions specific to this module.""" + pass + + +def get_svn_revision(config, commit): + """Works in both git and git-svn. returns a string.""" + svn_format = ( + '(git-svn-id: [^@ ]+@|SVN changes up to revision |' + 'LKGR w/ DEPS up to revision )(?P<return>[0-9]+)') + svn_revision = misc_utils.ReSearch.search_within_output( + config.verbose, svn_format, None, + [config.git, 'log', '-n', '1', '--format=format:%B', commit]) + if not svn_revision: + raise DepsRollError( + 'Revision number missing from Chromium origin/master.') + return int(svn_revision) + + +class SkiaGitCheckout(object): + """Class to create a temporary skia git checkout, if necessary. + """ + # pylint: disable=I0011,R0903 + + def __init__(self, config, depth): + self._config = config + self._depth = depth + self._use_temp = None + self._original_cwd = None + + def __enter__(self): + config = self._config + git = config.git + skia_dir = None + self._original_cwd = os.getcwd() + if config.skia_git_checkout_path: + if config.skia_git_checkout_path != os.curdir: + skia_dir = config.skia_git_checkout_path + ## Update origin/master if needed. + if self._config.verbose: + print '~~$', 'cd', skia_dir + os.chdir(skia_dir) + config.vsp.check_call([git, 'fetch', '-q', 'origin']) + self._use_temp = None + else: + skia_dir = tempfile.mkdtemp(prefix='git_skia_tmp_') + self._use_temp = skia_dir + try: + os.chdir(skia_dir) + config.vsp.check_call( + [git, 'clone', '-q', '--depth=%d' % self._depth, + '--single-branch', config.skia_url, '.']) + except (OSError, subprocess.CalledProcessError) as error: + shutil.rmtree(skia_dir) + raise error + + def __exit__(self, etype, value, traceback): + if self._config.skia_git_checkout_path != os.curdir: + if self._config.verbose: + print '~~$', 'cd', self._original_cwd + os.chdir(self._original_cwd) + if self._use_temp: + shutil.rmtree(self._use_temp) + + +def revision_and_hash(config): + """Finds revision number and git hash of origin/master in the Skia tree. + + Args: + config: (roll_deps.DepsRollConfig) object containing options. + + Returns: + A tuple (revision, hash) + revision: (int) SVN revision number. + git_hash: (string) full Git commit hash. + + Raises: + roll_deps.DepsRollError: if the revision can't be found. + OSError: failed to execute git or git-cl. + subprocess.CalledProcessError: git returned unexpected status. + """ + with SkiaGitCheckout(config, 1): + revision = get_svn_revision(config, 'origin/master') + git_hash = config.vsp.strip_output( + [config.git, 'show-ref', 'origin/master', '--hash']) + if not git_hash: + raise DepsRollError('Git hash can not be found.') + return revision, git_hash + + +def revision_and_hash_from_revision(config, revision): + """Finds revision number and git hash of a commit in the Skia tree. + + Args: + config: (roll_deps.DepsRollConfig) object containing options. + revision: (int) SVN revision number. + + Returns: + A tuple (revision, hash) + revision: (int) SVN revision number. + git_hash: (string) full Git commit hash. + + Raises: + roll_deps.DepsRollError: if the revision can't be found. + OSError: failed to execute git or git-cl. + subprocess.CalledProcessError: git returned unexpected status. + """ + with SkiaGitCheckout(config, config.search_depth): + revision_regex = config.revision_format % revision + git_hash = config.vsp.strip_output( + [config.git, 'log', '--grep', revision_regex, + '--format=format:%H', 'origin/master']) + if not git_hash: + raise DepsRollError('Git hash can not be found.') + return revision, git_hash + + +def revision_and_hash_from_partial(config, partial_hash): + """Returns the SVN revision number and full git hash. + + Args: + config: (roll_deps.DepsRollConfig) object containing options. + partial_hash: (string) Partial git commit hash. + + Returns: + A tuple (revision, hash) + revision: (int) SVN revision number. + git_hash: (string) full Git commit hash. + + Raises: + roll_deps.DepsRollError: if the revision can't be found. + OSError: failed to execute git or git-cl. + subprocess.CalledProcessError: git returned unexpected status. + """ + with SkiaGitCheckout(config, config.search_depth): + git_hash = config.vsp.strip_output( + ['git', 'log', '-n', '1', '--format=format:%H', partial_hash]) + if not git_hash: + raise DepsRollError('Partial Git hash can not be found.') + revision = get_svn_revision(config, git_hash) + return revision, git_hash + + +def change_skia_deps(revision, git_hash, depspath): + """Update the DEPS file. + + Modify the skia_revision and skia_hash entries in the given DEPS file. + + Args: + revision: (int) Skia SVN revision. + git_hash: (string) Skia Git hash. + depspath: (string) path to DEPS file. + """ + temp_file = tempfile.NamedTemporaryFile(delete=False, + prefix='skia_DEPS_ROLL_tmp_') + try: + deps_regex_rev = re.compile('"skia_revision": "[0-9]*",') + deps_regex_hash = re.compile('"skia_hash": "[0-9a-f]*",') + + deps_regex_rev_repl = '"skia_revision": "%d",' % revision + deps_regex_hash_repl = '"skia_hash": "%s",' % git_hash + + with open(depspath, 'r') as input_stream: + for line in input_stream: + line = deps_regex_rev.sub(deps_regex_rev_repl, line) + line = deps_regex_hash.sub(deps_regex_hash_repl, line) + temp_file.write(line) + finally: + temp_file.close() + shutil.move(temp_file.name, depspath) + + +def git_cl_uploader(config, message, file_list): + """Create a commit in the current git branch; upload via git-cl. + + Assumes that you are already on the branch you want to be on. + + Args: + config: (roll_deps.DepsRollConfig) object containing options. + message: (string) the commit message, can be multiline. + file_list: (list of strings) list of filenames to pass to `git add`. + + Returns: + The output of `git cl issue`, if not config.skip_cl_upload, else ''. + """ + + git, vsp = config.git, config.vsp + svn_info = str(get_svn_revision(config, 'HEAD')) + + for filename in file_list: + assert os.path.exists(filename) + vsp.check_call([git, 'add', filename]) + + vsp.check_call([git, 'commit', '-q', '-m', message]) + + git_cl = [git, 'cl', 'upload', '-f', + '--bypass-hooks', '--bypass-watchlists'] + if config.cc_list: + git_cl.append('--cc=%s' % config.cc_list) + if config.reviewers_list: + git_cl.append('--reviewers=%s' % config.reviewers_list) + + git_try = [ + git, 'cl', 'try', '-m', 'tryserver.chromium', '--revision', svn_info] + git_try.extend([arg for bot in config.cl_bot_list for arg in ('-b', bot)]) + + branch_name = git_utils.git_branch_name(vsp.verbose) + + if config.skip_cl_upload: + space = ' ' + print 'You should call:' + print '%scd %s' % (space, os.getcwd()) + misc_utils.print_subprocess_args(space, [git, 'checkout', branch_name]) + misc_utils.print_subprocess_args(space, git_cl) + if config.cl_bot_list: + misc_utils.print_subprocess_args(space, git_try) + print + return '' + else: + vsp.check_call(git_cl) + issue = vsp.strip_output([git, 'cl', 'issue']) + if config.cl_bot_list: + vsp.check_call(git_try) + return issue + + +def roll_deps(config, revision, git_hash): + """Upload changed DEPS and a whitespace change. + + Given the correct git_hash, create two Reitveld issues. + + Args: + config: (roll_deps.DepsRollConfig) object containing options. + revision: (int) Skia SVN revision. + git_hash: (string) Skia Git hash. + + Returns: + a tuple containing textual description of the two issues. + + Raises: + OSError: failed to execute git or git-cl. + subprocess.CalledProcessError: git returned unexpected status. + """ + + git = config.git + with misc_utils.ChangeDir(config.chromium_path, config.verbose): + config.vsp.check_call([git, 'fetch', '-q', 'origin']) + + old_revision = misc_utils.ReSearch.search_within_output( + config.verbose, '"skia_revision": "(?P<return>[0-9]+)",', None, + [git, 'show', 'origin/master:DEPS']) + assert old_revision + if revision == int(old_revision): + print 'DEPS is up to date!' + return (None, None) + + master_hash = config.vsp.strip_output( + [git, 'show-ref', 'origin/master', '--hash']) + master_revision = get_svn_revision(config, 'origin/master') + + # master_hash[8] gives each whitespace CL a unique name. + if config.save_branches: + branch = 'control_%s' % master_hash[:8] + else: + branch = None + message = ('whitespace change %s\n\n' + 'Chromium base revision: %d / %s\n\n' + 'This CL was created by Skia\'s roll_deps.py script.\n' + ) % (master_hash[:8], master_revision, master_hash[:8]) + with git_utils.ChangeGitBranch(branch, 'origin/master', + config.verbose): + branch = git_utils.git_branch_name(config.vsp.verbose) + + with open('build/whitespace_file.txt', 'a') as output_stream: + output_stream.write('\nCONTROL\n') + + whitespace_cl = git_cl_uploader( + config, message, ['build/whitespace_file.txt']) + + control_url = misc_utils.ReSearch.search_within_string( + whitespace_cl, '(?P<return>https?://[^) ]+)', '?') + if config.save_branches: + whitespace_cl = '%s\n branch: %s' % (whitespace_cl, branch) + + if config.save_branches: + branch = 'roll_%d_%s' % (revision, master_hash[:8]) + else: + branch = None + message = ( + 'roll skia DEPS to %d\n\n' + 'Chromium base revision: %d / %s\n' + 'Old Skia revision: %s\n' + 'New Skia revision: %d\n' + 'Control CL: %s\n\n' + 'This CL was created by Skia\'s roll_deps.py script.\n\n' + 'Bypassing commit queue trybots:\n' + 'NOTRY=true\n' + % (revision, master_revision, master_hash[:8], + old_revision, revision, control_url)) + with git_utils.ChangeGitBranch(branch, 'origin/master', + config.verbose): + branch = git_utils.git_branch_name(config.vsp.verbose) + + change_skia_deps(revision, git_hash, 'DEPS') + deps_cl = git_cl_uploader(config, message, ['DEPS']) + if config.save_branches: + deps_cl = '%s\n branch: %s' % (deps_cl, branch) + + return deps_cl, whitespace_cl + + +def find_hash_and_roll_deps(config, revision=None, partial_hash=None): + """Call find_hash_from_revision() and roll_deps(). + + The calls to git will be verbose on standard output. After a + successful upload of both issues, print links to the new + codereview issues. + + Args: + config: (roll_deps.DepsRollConfig) object containing options. + revision: (int or None) the Skia SVN revision number or None + to use the tip of the tree. + partial_hash: (string or None) a partial pure-git Skia commit + hash. Don't pass both partial_hash and revision. + + Raises: + roll_deps.DepsRollError: if the revision can't be found. + OSError: failed to execute git or git-cl. + subprocess.CalledProcessError: git returned unexpected status. + """ + + if revision and partial_hash: + raise DepsRollError('Pass revision or partial_hash, not both.') + + if partial_hash: + revision, git_hash = revision_and_hash_from_partial( + config, partial_hash) + elif revision: + revision, git_hash = revision_and_hash_from_revision(config, revision) + else: + revision, git_hash = revision_and_hash(config) + + print 'revision=%r\nhash=%r\n' % (revision, git_hash) + + deps_issue, whitespace_issue = roll_deps(config, revision, git_hash) + + if deps_issue and whitespace_issue: + print 'DEPS roll:\n %s\n' % deps_issue + print 'Whitespace change:\n %s\n' % whitespace_issue + else: + print >> sys.stderr, 'No issues created.' + + +def main(args): + """main function; see module-level docstring and GetOptionParser help. + + Args: + args: sys.argv[1:]-type argument list. + """ + option_parser = DepsRollConfig.GetOptionParser() + options = option_parser.parse_args(args)[0] + + if not options.chromium_path: + option_parser.error('Must specify chromium_path.') + if not os.path.isdir(options.chromium_path): + option_parser.error('chromium_path must be a directory.') + + if not git_utils.git_executable(): + option_parser.error('Invalid git executable.') + + config = DepsRollConfig(options) + find_hash_and_roll_deps(config, options.revision, options.git_hash) + + +if __name__ == '__main__': + main(sys.argv[1:]) + diff --git a/chromium/third_party/skia/tools/sanitize_source_files.py b/chromium/third_party/skia/tools/sanitize_source_files.py new file mode 100755 index 00000000000..9fd6d3272d0 --- /dev/null +++ b/chromium/third_party/skia/tools/sanitize_source_files.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Module that sanitizes source files with specified modifiers.""" + + +import commands +import os +import sys + + +_FILE_EXTENSIONS_TO_SANITIZE = ['cpp', 'h', 'c', 'gyp', 'gypi'] + +_SUBDIRS_TO_IGNORE = ['.git', '.svn', 'third_party'] + + +def SanitizeFilesWithModifiers(directory, file_modifiers, line_modifiers): + """Sanitizes source files with the specified file and line modifiers. + + Args: + directory: string - The directory which will be recursively traversed to + find source files to apply modifiers to. + file_modifiers: list - file-modification methods which should be applied to + the complete file content (Eg: EOFOneAndOnlyOneNewlineAdder). + line_modifiers: list - line-modification methods which should be applied to + lines in a file (Eg: TabReplacer). + """ + for item in os.listdir(directory): + + full_item_path = os.path.join(directory, item) + + if os.path.isfile(full_item_path): # Item is a file. + + # Only sanitize files with extensions we care about. + if (len(full_item_path.split('.')) > 1 and + full_item_path.split('.')[-1] in _FILE_EXTENSIONS_TO_SANITIZE): + f = file(full_item_path) + try: + lines = f.readlines() + finally: + f.close() + + new_lines = [] # Collect changed lines here. + line_number = 0 # Keeps track of line numbers in the source file. + write_to_file = False # File is written to only if this flag is set. + + # Run the line modifiers for each line in this file. + for line in lines: + original_line = line + line_number += 1 + + for modifier in line_modifiers: + line = modifier(line, full_item_path, line_number) + if original_line != line: + write_to_file = True + new_lines.append(line) + + # Run the file modifiers. + old_content = ''.join(lines) + new_content = ''.join(new_lines) + for modifier in file_modifiers: + new_content = modifier(new_content, full_item_path) + if new_content != old_content: + write_to_file = True + + # Write modifications to the file. + if write_to_file: + f = file(full_item_path, 'w') + try: + f.write(new_content) + finally: + f.close() + print 'Made changes to %s' % full_item_path + + elif item not in _SUBDIRS_TO_IGNORE: + # Item is a directory recursively call the method. + SanitizeFilesWithModifiers(full_item_path, file_modifiers, line_modifiers) + + +############## Line Modification methods ############## + + +def TrailingWhitespaceRemover(line, file_path, line_number): + """Strips out trailing whitespaces from the specified line.""" + stripped_line = line.rstrip() + '\n' + if line != stripped_line: + print 'Removing trailing whitespace in %s:%s' % (file_path, line_number) + return stripped_line + + +def CrlfReplacer(line, file_path, line_number): + """Replaces CRLF with LF.""" + if '\r\n' in line: + print 'Replacing CRLF with LF in %s:%s' % (file_path, line_number) + return line.replace('\r\n', '\n') + + +def TabReplacer(line, file_path, line_number): + """Replaces Tabs with 4 whitespaces.""" + if '\t' in line: + print 'Replacing Tab with whitespace in %s:%s' % (file_path, line_number) + return line.replace('\t', ' ') + + +############## File Modification methods ############## + + +def CopywriteChecker(file_content, unused_file_path): + """Ensures that the copywrite information is correct.""" + # TODO(rmistry): Figure out the legal implications of changing old copyright + # headers. + return file_content + + +def EOFOneAndOnlyOneNewlineAdder(file_content, file_path): + """Adds one and only one LF at the end of the file.""" + if file_content and (file_content[-1] != '\n' or file_content[-2:-1] == '\n'): + file_content = file_content.rstrip() + file_content += '\n' + print 'Added exactly one newline to %s' % file_path + return file_content + + +def SvnEOLChecker(file_content, file_path): + """Sets svn:eol-style property to LF.""" + output = commands.getoutput( + 'svn propget svn:eol-style %s' % file_path) + if output != 'LF': + print 'Setting svn:eol-style property to LF in %s' % file_path + os.system('svn ps svn:eol-style LF %s' % file_path) + return file_content + + +####################################################### + + +if '__main__' == __name__: + sys.exit(SanitizeFilesWithModifiers( + os.getcwd(), + file_modifiers=[ + CopywriteChecker, + EOFOneAndOnlyOneNewlineAdder, + SvnEOLChecker, + ], + line_modifiers=[ + CrlfReplacer, + TabReplacer, + TrailingWhitespaceRemover, + ], + )) diff --git a/chromium/third_party/skia/tools/sk_tool_utils.cpp b/chromium/third_party/skia/tools/sk_tool_utils.cpp new file mode 100644 index 00000000000..3eb55554b6d --- /dev/null +++ b/chromium/third_party/skia/tools/sk_tool_utils.cpp @@ -0,0 +1,32 @@ +#include "sk_tool_utils.h" + +namespace sk_tool_utils { + +const char* colortype_name(SkColorType ct) { + switch (ct) { + case kUnknown_SkColorType: return "Unknown"; + case kAlpha_8_SkColorType: return "Alpha_8"; + case kIndex_8_SkColorType: return "Index_8"; + case kARGB_4444_SkColorType: return "ARGB_4444"; + case kRGB_565_SkColorType: return "RGB_565"; + case kRGBA_8888_SkColorType: return "RGBA_8888"; + case kBGRA_8888_SkColorType: return "BGRA_8888"; + default: + SkASSERT(false); + return "unexpected colortype"; + } +} + +void write_pixels(SkCanvas* canvas, const SkBitmap& bitmap, int x, int y, + SkColorType colorType, SkAlphaType alphaType) { + SkBitmap tmp(bitmap); + tmp.lockPixels(); + + SkImageInfo info = tmp.info(); + info.fColorType = colorType; + info.fAlphaType = alphaType; + + canvas->writePixels(info, tmp.getPixels(), tmp.rowBytes(), x, y); +} + +} diff --git a/chromium/third_party/skia/tools/sk_tool_utils.h b/chromium/third_party/skia/tools/sk_tool_utils.h new file mode 100644 index 00000000000..48fd716342b --- /dev/null +++ b/chromium/third_party/skia/tools/sk_tool_utils.h @@ -0,0 +1,25 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef sk_tool_utils_DEFINED +#define sk_tool_utils_DEFINED + +#include "SkCanvas.h" +#include "SkBitmap.h" + +namespace sk_tool_utils { + + const char* colortype_name(SkColorType); + + /** + * Call canvas->writePixels() by using the pixels from bitmap, but with an info that claims + * the pixels are colorType + alphaType + */ + void write_pixels(SkCanvas*, const SkBitmap&, int x, int y, SkColorType, SkAlphaType); +} + +#endif diff --git a/chromium/third_party/skia/tools/skdiff.cpp b/chromium/third_party/skia/tools/skdiff.cpp new file mode 100644 index 00000000000..ae6d72cd7a0 --- /dev/null +++ b/chromium/third_party/skia/tools/skdiff.cpp @@ -0,0 +1,228 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "skdiff.h" +#include "SkBitmap.h" +#include "SkColor.h" +#include "SkColorPriv.h" +#include "SkTypes.h" + +/*static*/ char const * const DiffRecord::ResultNames[DiffRecord::kResultCount] = { + "EqualBits", + "EqualPixels", + "DifferentPixels", + "DifferentSizes", + "CouldNotCompare", + "Unknown", +}; + +DiffRecord::Result DiffRecord::getResultByName(const char *name) { + for (int result = 0; result < DiffRecord::kResultCount; ++result) { + if (0 == strcmp(DiffRecord::ResultNames[result], name)) { + return static_cast<DiffRecord::Result>(result); + } + } + return DiffRecord::kResultCount; +} + +static char const * const ResultDescriptions[DiffRecord::kResultCount] = { + "contain exactly the same bits", + "contain the same pixel values, but not the same bits", + "have identical dimensions but some differing pixels", + "have differing dimensions", + "could not be compared", + "not compared yet", +}; + +const char* DiffRecord::getResultDescription(DiffRecord::Result result) { + return ResultDescriptions[result]; +} + +/*static*/ char const * const DiffResource::StatusNames[DiffResource::kStatusCount] = { + "Decoded", + "CouldNotDecode", + + "Read", + "CouldNotRead", + + "Exists", + "DoesNotExist", + + "Specified", + "Unspecified", + + "Unknown", +}; + +DiffResource::Status DiffResource::getStatusByName(const char *name) { + for (int status = 0; status < DiffResource::kStatusCount; ++status) { + if (0 == strcmp(DiffResource::StatusNames[status], name)) { + return static_cast<DiffResource::Status>(status); + } + } + return DiffResource::kStatusCount; +} + +static char const * const StatusDescriptions[DiffResource::kStatusCount] = { + "decoded", + "could not be decoded", + + "read", + "could not be read", + + "found", + "not found", + + "specified", + "unspecified", + + "unknown", +}; + +const char* DiffResource::getStatusDescription(DiffResource::Status status) { + return StatusDescriptions[status]; +} + +bool DiffResource::isStatusFailed(DiffResource::Status status) { + return DiffResource::kCouldNotDecode_Status == status || + DiffResource::kCouldNotRead_Status == status || + DiffResource::kDoesNotExist_Status == status || + DiffResource::kUnspecified_Status == status || + DiffResource::kUnknown_Status == status; +} + +bool DiffResource::getMatchingStatuses(char* selector, bool statuses[kStatusCount]) { + if (!strcmp(selector, "any")) { + for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) { + statuses[statusIndex] = true; + } + return true; + } + + for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) { + statuses[statusIndex] = false; + } + + static const char kDelimiterChar = ','; + bool understood = true; + while (true) { + char* delimiterPtr = strchr(selector, kDelimiterChar); + + if (delimiterPtr) { + *delimiterPtr = '\0'; + } + + if (!strcmp(selector, "failed")) { + for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) { + Status status = static_cast<Status>(statusIndex); + statuses[statusIndex] |= isStatusFailed(status); + } + } else { + Status status = getStatusByName(selector); + if (status == kStatusCount) { + understood = false; + } else { + statuses[status] = true; + } + } + + if (!delimiterPtr) { + break; + } + + *delimiterPtr = kDelimiterChar; + selector = delimiterPtr + 1; + } + return understood; +} + +static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1, const int threshold) { + int da = SkGetPackedA32(c0) - SkGetPackedA32(c1); + int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1); + int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1); + int db = SkGetPackedB32(c0) - SkGetPackedB32(c1); + + return ((SkAbs32(da) <= threshold) && + (SkAbs32(dr) <= threshold) && + (SkAbs32(dg) <= threshold) && + (SkAbs32(db) <= threshold)); +} + +const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE); +const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK); + +void compute_diff(DiffRecord* dr, DiffMetricProc diffFunction, const int colorThreshold) { + const int w = dr->fComparison.fBitmap.width(); + const int h = dr->fComparison.fBitmap.height(); + if (w != dr->fBase.fBitmap.width() || h != dr->fBase.fBitmap.height()) { + dr->fResult = DiffRecord::kDifferentSizes_Result; + return; + } + + SkAutoLockPixels alpDiff(dr->fDifference.fBitmap); + SkAutoLockPixels alpWhite(dr->fWhite.fBitmap); + int mismatchedPixels = 0; + int totalMismatchA = 0; + int totalMismatchR = 0; + int totalMismatchG = 0; + int totalMismatchB = 0; + + // Accumulate fractionally different pixels, then divide out + // # of pixels at the end. + dr->fWeightedFraction = 0; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + SkPMColor c0 = *dr->fBase.fBitmap.getAddr32(x, y); + SkPMColor c1 = *dr->fComparison.fBitmap.getAddr32(x, y); + SkPMColor outputDifference = diffFunction(c0, c1); + uint32_t thisA = SkAbs32(SkGetPackedA32(c0) - SkGetPackedA32(c1)); + uint32_t thisR = SkAbs32(SkGetPackedR32(c0) - SkGetPackedR32(c1)); + uint32_t thisG = SkAbs32(SkGetPackedG32(c0) - SkGetPackedG32(c1)); + uint32_t thisB = SkAbs32(SkGetPackedB32(c0) - SkGetPackedB32(c1)); + totalMismatchA += thisA; + totalMismatchR += thisR; + totalMismatchG += thisG; + totalMismatchB += thisB; + // In HSV, value is defined as max RGB component. + int value = MAX3(thisR, thisG, thisB); + dr->fWeightedFraction += ((float) value) / 255; + if (thisA > dr->fMaxMismatchA) { + dr->fMaxMismatchA = thisA; + } + if (thisR > dr->fMaxMismatchR) { + dr->fMaxMismatchR = thisR; + } + if (thisG > dr->fMaxMismatchG) { + dr->fMaxMismatchG = thisG; + } + if (thisB > dr->fMaxMismatchB) { + dr->fMaxMismatchB = thisB; + } + if (!colors_match_thresholded(c0, c1, colorThreshold)) { + mismatchedPixels++; + *dr->fDifference.fBitmap.getAddr32(x, y) = outputDifference; + *dr->fWhite.fBitmap.getAddr32(x, y) = PMCOLOR_WHITE; + } else { + *dr->fDifference.fBitmap.getAddr32(x, y) = 0; + *dr->fWhite.fBitmap.getAddr32(x, y) = PMCOLOR_BLACK; + } + } + } + if (0 == mismatchedPixels) { + dr->fResult = DiffRecord::kEqualPixels_Result; + return; + } + dr->fResult = DiffRecord::kDifferentPixels_Result; + int pixelCount = w * h; + dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount; + dr->fWeightedFraction /= pixelCount; + dr->fTotalMismatchA = totalMismatchA; + dr->fAverageMismatchA = ((float) totalMismatchA) / pixelCount; + dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount; + dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount; + dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount; +} diff --git a/chromium/third_party/skia/tools/skdiff.h b/chromium/third_party/skia/tools/skdiff.h new file mode 100644 index 00000000000..6abaf6c4056 --- /dev/null +++ b/chromium/third_party/skia/tools/skdiff.h @@ -0,0 +1,272 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef skdiff_DEFINED +#define skdiff_DEFINED + +#include "SkBitmap.h" +#include "SkColor.h" +#include "SkColorPriv.h" +#include "SkString.h" +#include "SkTDArray.h" + +#if SK_BUILD_FOR_WIN32 + #define PATH_DIV_STR "\\" + #define PATH_DIV_CHAR '\\' +#else + #define PATH_DIV_STR "/" + #define PATH_DIV_CHAR '/' +#endif + +#define MAX2(a,b) (((b) < (a)) ? (a) : (b)) +#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c))) + + +struct DiffResource { + enum Status { + /** The resource was specified, exists, read, and decoded. */ + kDecoded_Status, + /** The resource was specified, exists, read, but could not be decoded. */ + kCouldNotDecode_Status, + + /** The resource was specified, exists, and read. */ + kRead_Status, + /** The resource was specified, exists, but could not be read. */ + kCouldNotRead_Status, + + /** The resource was specified and exists. */ + kExists_Status, + /** The resource was specified, but does not exist. */ + kDoesNotExist_Status, + + /** The resource was specified. */ + kSpecified_Status, + /** The resource was not specified. */ + kUnspecified_Status, + + /** Nothing is yet known about the resource. */ + kUnknown_Status, + + /** NOT A VALID VALUE -- used to set up arrays and to represent an unknown value. */ + kStatusCount + }; + static char const * const StatusNames[DiffResource::kStatusCount]; + + /** Returns the Status with this name. + * If there is no Status with this name, returns kStatusCount. + */ + static Status getStatusByName(const char *name); + + /** Returns a text description of the given Status type. */ + static const char *getStatusDescription(Status status); + + /** Returns true if the Status indicates some kind of failure. */ + static bool isStatusFailed(Status status); + + /** Sets statuses[i] if it is implied by selector, unsets it if not. + * Selector may be a comma delimited list of status names, "any", or "failed". + * Returns true if the selector was entirely understood, false otherwise. + */ + static bool getMatchingStatuses(char* selector, bool statuses[kStatusCount]); + + DiffResource() : fFilename(), fFullPath(), fBitmap(), fStatus(kUnknown_Status) { }; + + /** If isEmpty() indicates no filename available. */ + SkString fFilename; + /** If isEmpty() indicates no path available. */ + SkString fFullPath; + /** If empty() indicates the bitmap could not be created. */ + SkBitmap fBitmap; + Status fStatus; +}; + +struct DiffRecord { + + // Result of comparison for each pair of files. + // Listed from "better" to "worse", for sorting of results. + enum Result { + kEqualBits_Result, + kEqualPixels_Result, + kDifferentPixels_Result, + kDifferentSizes_Result, + kCouldNotCompare_Result, + kUnknown_Result, + + kResultCount // NOT A VALID VALUE--used to set up arrays. Must be last. + }; + static char const * const ResultNames[DiffRecord::kResultCount]; + + /** Returns the Result with this name. + * If there is no Result with this name, returns kResultCount. + */ + static Result getResultByName(const char *name); + + /** Returns a text description of the given Result type. */ + static const char *getResultDescription(Result result); + + DiffRecord() + : fBase() + , fComparison() + , fDifference() + , fWhite() + , fFractionDifference(0) + , fWeightedFraction(0) + , fAverageMismatchA(0) + , fAverageMismatchR(0) + , fAverageMismatchG(0) + , fAverageMismatchB(0) + , fTotalMismatchA(0) + , fMaxMismatchA(0) + , fMaxMismatchR(0) + , fMaxMismatchG(0) + , fMaxMismatchB(0) + , fResult(kUnknown_Result) { + }; + + DiffResource fBase; + DiffResource fComparison; + DiffResource fDifference; + DiffResource fWhite; + + /// Arbitrary floating-point metric to be used to sort images from most + /// to least different from baseline; values of 0 will be omitted from the + /// summary webpage. + float fFractionDifference; + float fWeightedFraction; + + float fAverageMismatchA; + float fAverageMismatchR; + float fAverageMismatchG; + float fAverageMismatchB; + + uint32_t fTotalMismatchA; + + uint32_t fMaxMismatchA; + uint32_t fMaxMismatchR; + uint32_t fMaxMismatchG; + uint32_t fMaxMismatchB; + + /// Which category of diff result. + Result fResult; +}; + +typedef SkTDArray<DiffRecord*> RecordArray; + +/// A wrapper for any sortProc (comparison routine) which applies a first-order +/// sort beforehand, and a tiebreaker if the sortProc returns 0. +template<typename T> static int compare(const void* untyped_lhs, const void* untyped_rhs) { + const DiffRecord* lhs = *reinterpret_cast<DiffRecord* const *>(untyped_lhs); + const DiffRecord* rhs = *reinterpret_cast<DiffRecord* const *>(untyped_rhs); + + // First-order sort... these comparisons should be applied before comparing + // pixel values, no matter what. + if (lhs->fResult != rhs->fResult) { + return (lhs->fResult < rhs->fResult) ? 1 : -1; + } + + // Passed first-order sort, so call the pixel comparison routine. + int result = T::comparePixels(lhs, rhs); + if (result != 0) { + return result; + } + + // Tiebreaker... if we got to this point, we don't really care + // which order they are sorted in, but let's at least be consistent. + return strcmp(lhs->fBase.fFilename.c_str(), rhs->fBase.fFilename.c_str()); +} + +/// Comparison routine for qsort; sorts by fFractionDifference +/// from largest to smallest. +class CompareDiffMetrics { +public: + static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) { + if (lhs->fFractionDifference < rhs->fFractionDifference) { + return 1; + } + if (rhs->fFractionDifference < lhs->fFractionDifference) { + return -1; + } + return 0; + } +}; + +class CompareDiffWeighted { +public: + static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) { + if (lhs->fWeightedFraction < rhs->fWeightedFraction) { + return 1; + } + if (lhs->fWeightedFraction > rhs->fWeightedFraction) { + return -1; + } + return 0; + } +}; + +/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB}) +/// from largest to smallest. +class CompareDiffMeanMismatches { +public: + static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) { + float leftValue = MAX3(lhs->fAverageMismatchR, + lhs->fAverageMismatchG, + lhs->fAverageMismatchB); + float rightValue = MAX3(rhs->fAverageMismatchR, + rhs->fAverageMismatchG, + rhs->fAverageMismatchB); + if (leftValue < rightValue) { + return 1; + } + if (rightValue < leftValue) { + return -1; + } + return 0; + } +}; + +/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB}) +/// from largest to smallest. +class CompareDiffMaxMismatches { +public: + static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) { + uint32_t leftValue = MAX3(lhs->fMaxMismatchR, + lhs->fMaxMismatchG, + lhs->fMaxMismatchB); + uint32_t rightValue = MAX3(rhs->fMaxMismatchR, + rhs->fMaxMismatchG, + rhs->fMaxMismatchB); + if (leftValue < rightValue) { + return 1; + } + if (rightValue < leftValue) { + return -1; + } + + return CompareDiffMeanMismatches::comparePixels(lhs, rhs); + } +}; + + +/// Parameterized routine to compute the color of a pixel in a difference image. +typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor); + +// from gm +static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) { + int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1); + int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1); + int db = SkGetPackedB32(c0) - SkGetPackedB32(c1); + + return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db)); +} + +/** When finished, dr->fResult should have some value other than kUnknown_Result. + * Expects dr->fWhite.fBitmap and dr->fDifference.fBitmap to have the same bounds as + * dr->fBase.fBitmap and have a valid pixelref. + */ +void compute_diff(DiffRecord* dr, DiffMetricProc diffFunction, const int colorThreshold); + +#endif diff --git a/chromium/third_party/skia/tools/skdiff_html.cpp b/chromium/third_party/skia/tools/skdiff_html.cpp new file mode 100644 index 00000000000..6f3c3b09e15 --- /dev/null +++ b/chromium/third_party/skia/tools/skdiff_html.cpp @@ -0,0 +1,313 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "skdiff.h" +#include "skdiff_html.h" +#include "SkStream.h" +#include "SkTime.h" + +/// Make layout more consistent by scaling image to 240 height, 360 width, +/// or natural size, whichever is smallest. +static int compute_image_height(int height, int width) { + int retval = 240; + if (height < retval) { + retval = height; + } + float scale = (float) retval / height; + if (width * scale > 360) { + scale = (float) 360 / width; + retval = static_cast<int>(height * scale); + } + return retval; +} + +static void print_table_header(SkFILEWStream* stream, + const int matchCount, + const int colorThreshold, + const RecordArray& differences, + const SkString &baseDir, + const SkString &comparisonDir, + bool doOutputDate = false) { + stream->writeText("<table>\n"); + stream->writeText("<tr><th>"); + stream->writeText("select image</th>\n<th>"); + if (doOutputDate) { + SkTime::DateTime dt; + SkTime::GetDateTime(&dt); + stream->writeText("SkDiff run at "); + stream->writeDecAsText(dt.fHour); + stream->writeText(":"); + if (dt.fMinute < 10) { + stream->writeText("0"); + } + stream->writeDecAsText(dt.fMinute); + stream->writeText(":"); + if (dt.fSecond < 10) { + stream->writeText("0"); + } + stream->writeDecAsText(dt.fSecond); + stream->writeText("<br>"); + } + stream->writeDecAsText(matchCount); + stream->writeText(" of "); + stream->writeDecAsText(differences.count()); + stream->writeText(" diffs matched "); + if (colorThreshold == 0) { + stream->writeText("exactly"); + } else { + stream->writeText("within "); + stream->writeDecAsText(colorThreshold); + stream->writeText(" color units per component"); + } + stream->writeText(".<br>"); + stream->writeText("</th>\n<th>"); + stream->writeText("every different pixel shown in white"); + stream->writeText("</th>\n<th>"); + stream->writeText("color difference at each pixel"); + stream->writeText("</th>\n<th>baseDir: "); + stream->writeText(baseDir.c_str()); + stream->writeText("</th>\n<th>comparisonDir: "); + stream->writeText(comparisonDir.c_str()); + stream->writeText("</th>\n"); + stream->writeText("</tr>\n"); +} + +static void print_pixel_count(SkFILEWStream* stream, const DiffRecord& diff) { + stream->writeText("<br>("); + stream->writeDecAsText(static_cast<int>(diff.fFractionDifference * + diff.fBase.fBitmap.width() * + diff.fBase.fBitmap.height())); + stream->writeText(" pixels)"); +/* + stream->writeDecAsText(diff.fWeightedFraction * + diff.fBaseWidth * + diff.fBaseHeight); + stream->writeText(" weighted pixels)"); +*/ +} + +static void print_checkbox_cell(SkFILEWStream* stream, const DiffRecord& diff) { + stream->writeText("<td><input type=\"checkbox\" name=\""); + stream->writeText(diff.fBase.fFilename.c_str()); + stream->writeText("\" checked=\"yes\"></td>"); +} + +static void print_label_cell(SkFILEWStream* stream, const DiffRecord& diff) { + char metricBuf [20]; + + stream->writeText("<td><b>"); + stream->writeText(diff.fBase.fFilename.c_str()); + stream->writeText("</b><br>"); + switch (diff.fResult) { + case DiffRecord::kEqualBits_Result: + SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here"); + return; + case DiffRecord::kEqualPixels_Result: + SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here"); + return; + case DiffRecord::kDifferentSizes_Result: + stream->writeText("Image sizes differ</td>"); + return; + case DiffRecord::kDifferentPixels_Result: + sprintf(metricBuf, "%.4f%%", 100 * diff.fFractionDifference); + stream->writeText(metricBuf); + stream->writeText(" of pixels differ"); + stream->writeText("\n ("); + sprintf(metricBuf, "%.4f%%", 100 * diff.fWeightedFraction); + stream->writeText(metricBuf); + stream->writeText(" weighted)"); + // Write the actual number of pixels that differ if it's < 1% + if (diff.fFractionDifference < 0.01) { + print_pixel_count(stream, diff); + } + stream->writeText("<br>"); + if (SkScalarRoundToInt(diff.fAverageMismatchA) > 0) { + stream->writeText("<br>Average alpha channel mismatch "); + stream->writeDecAsText(SkScalarRoundToInt(diff.fAverageMismatchA)); + } + + stream->writeText("<br>Max alpha channel mismatch "); + stream->writeDecAsText(SkScalarRoundToInt(diff.fMaxMismatchA)); + + stream->writeText("<br>Total alpha channel mismatch "); + stream->writeDecAsText(static_cast<int>(diff.fTotalMismatchA)); + + stream->writeText("<br>"); + stream->writeText("<br>Average color mismatch "); + stream->writeDecAsText(SkScalarRoundToInt(MAX3(diff.fAverageMismatchR, + diff.fAverageMismatchG, + diff.fAverageMismatchB))); + stream->writeText("<br>Max color mismatch "); + stream->writeDecAsText(MAX3(diff.fMaxMismatchR, + diff.fMaxMismatchG, + diff.fMaxMismatchB)); + stream->writeText("</td>"); + break; + case DiffRecord::kCouldNotCompare_Result: + stream->writeText("Could not compare.<br>base: "); + stream->writeText(DiffResource::getStatusDescription(diff.fBase.fStatus)); + stream->writeText("<br>comparison: "); + stream->writeText(DiffResource::getStatusDescription(diff.fComparison.fStatus)); + stream->writeText("</td>"); + return; + default: + SkDEBUGFAIL("encountered DiffRecord with unknown result type"); + return; + } +} + +static void print_image_cell(SkFILEWStream* stream, const SkString& path, int height) { + stream->writeText("<td><a href=\""); + stream->writeText(path.c_str()); + stream->writeText("\"><img src=\""); + stream->writeText(path.c_str()); + stream->writeText("\" height=\""); + stream->writeDecAsText(height); + stream->writeText("px\"></a></td>"); +} + +static void print_link_cell(SkFILEWStream* stream, const SkString& path, const char* text) { + stream->writeText("<td><a href=\""); + stream->writeText(path.c_str()); + stream->writeText("\">"); + stream->writeText(text); + stream->writeText("</a></td>"); +} + +static void print_diff_resource_cell(SkFILEWStream* stream, DiffResource& resource, + const SkString& relativePath, bool local) { + if (resource.fBitmap.empty()) { + if (DiffResource::kCouldNotDecode_Status == resource.fStatus) { + if (local && !resource.fFilename.isEmpty()) { + print_link_cell(stream, resource.fFilename, "N/A"); + return; + } + if (!resource.fFullPath.isEmpty()) { + if (!resource.fFullPath.startsWith(PATH_DIV_STR)) { + resource.fFullPath.prepend(relativePath); + } + print_link_cell(stream, resource.fFullPath, "N/A"); + return; + } + } + stream->writeText("<td>N/A</td>"); + return; + } + + int height = compute_image_height(resource.fBitmap.height(), resource.fBitmap.width()); + if (local) { + print_image_cell(stream, resource.fFilename, height); + return; + } + if (!resource.fFullPath.startsWith(PATH_DIV_STR)) { + resource.fFullPath.prepend(relativePath); + } + print_image_cell(stream, resource.fFullPath, height); +} + +static void print_diff_row(SkFILEWStream* stream, DiffRecord& diff, const SkString& relativePath) { + stream->writeText("<tr>\n"); + print_checkbox_cell(stream, diff); + print_label_cell(stream, diff); + print_diff_resource_cell(stream, diff.fWhite, relativePath, true); + print_diff_resource_cell(stream, diff.fDifference, relativePath, true); + print_diff_resource_cell(stream, diff.fBase, relativePath, false); + print_diff_resource_cell(stream, diff.fComparison, relativePath, false); + stream->writeText("</tr>\n"); + stream->flush(); +} + +void print_diff_page(const int matchCount, + const int colorThreshold, + const RecordArray& differences, + const SkString& baseDir, + const SkString& comparisonDir, + const SkString& outputDir) { + + SkASSERT(!baseDir.isEmpty()); + SkASSERT(!comparisonDir.isEmpty()); + SkASSERT(!outputDir.isEmpty()); + + SkString outputPath(outputDir); + outputPath.append("index.html"); + //SkFILEWStream outputStream ("index.html"); + SkFILEWStream outputStream(outputPath.c_str()); + + // Need to convert paths from relative-to-cwd to relative-to-outputDir + // FIXME this doesn't work if there are '..' inside the outputDir + + bool isPathAbsolute = false; + // On Windows or Linux, a path starting with PATH_DIV_CHAR is absolute. + if (outputDir.size() > 0 && PATH_DIV_CHAR == outputDir[0]) { + isPathAbsolute = true; + } +#ifdef SK_BUILD_FOR_WIN32 + // On Windows, absolute paths can also start with "x:", where x is any + // drive letter. + if (outputDir.size() > 1 && ':' == outputDir[1]) { + isPathAbsolute = true; + } +#endif + + SkString relativePath; + if (!isPathAbsolute) { + unsigned int ui; + for (ui = 0; ui < outputDir.size(); ui++) { + if (outputDir[ui] == PATH_DIV_CHAR) { + relativePath.append(".." PATH_DIV_STR); + } + } + } + + outputStream.writeText( + "<html>\n<head>\n" + "<script src=\"https://ajax.googleapis.com/ajax/" + "libs/jquery/1.7.2/jquery.min.js\"></script>\n" + "<script type=\"text/javascript\">\n" + "function generateCheckedList() {\n" + "var boxes = $(\":checkbox:checked\");\n" + "var fileCmdLineString = '';\n" + "var fileMultiLineString = '';\n" + "for (var i = 0; i < boxes.length; i++) {\n" + "fileMultiLineString += boxes[i].name + '<br>';\n" + "fileCmdLineString += boxes[i].name + ' ';\n" + "}\n" + "$(\"#checkedList\").html(fileCmdLineString + " + "'<br><br>' + fileMultiLineString);\n" + "}\n" + "</script>\n</head>\n<body>\n"); + print_table_header(&outputStream, matchCount, colorThreshold, differences, + baseDir, comparisonDir); + int i; + for (i = 0; i < differences.count(); i++) { + DiffRecord* diff = differences[i]; + + switch (diff->fResult) { + // Cases in which there is no diff to report. + case DiffRecord::kEqualBits_Result: + case DiffRecord::kEqualPixels_Result: + continue; + // Cases in which we want a detailed pixel diff. + case DiffRecord::kDifferentPixels_Result: + case DiffRecord::kDifferentSizes_Result: + case DiffRecord::kCouldNotCompare_Result: + print_diff_row(&outputStream, *diff, relativePath); + continue; + default: + SkDEBUGFAIL("encountered DiffRecord with unknown result type"); + continue; + } + } + outputStream.writeText( + "</table>\n" + "<input type=\"button\" " + "onclick=\"generateCheckedList()\" " + "value=\"Create Rebaseline List\">\n" + "<div id=\"checkedList\"></div>\n" + "</body>\n</html>\n"); + outputStream.flush(); +} diff --git a/chromium/third_party/skia/tools/skdiff_html.h b/chromium/third_party/skia/tools/skdiff_html.h new file mode 100644 index 00000000000..eefbebf2fd8 --- /dev/null +++ b/chromium/third_party/skia/tools/skdiff_html.h @@ -0,0 +1,21 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef skdiff_html_DEFINED +#define skdiff_html_DEFINED + +#include "skdiff.h" +class SkString; + +void print_diff_page(const int matchCount, + const int colorThreshold, + const RecordArray& differences, + const SkString& baseDir, + const SkString& comparisonDir, + const SkString& outputDir); + +#endif diff --git a/chromium/third_party/skia/tools/skdiff_image.cpp b/chromium/third_party/skia/tools/skdiff_image.cpp new file mode 100644 index 00000000000..172f62b555b --- /dev/null +++ b/chromium/third_party/skia/tools/skdiff_image.cpp @@ -0,0 +1,377 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "skdiff.h" +#include "skdiff_utils.h" +#include "SkBitmap.h" +#include "SkData.h" +#include "SkImageDecoder.h" +#include "SkImageEncoder.h" +#include "SkOSFile.h" +#include "SkTDArray.h" +#include "SkTemplates.h" +#include "SkTypes.h" + +#include <stdio.h> + +/// If outputDir.isEmpty(), don't write out diff files. +static void create_diff_images (DiffMetricProc dmp, + const int colorThreshold, + const SkString& baseFile, + const SkString& comparisonFile, + const SkString& outputDir, + const SkString& outputFilename, + DiffRecord* drp) { + SkASSERT(!baseFile.isEmpty()); + SkASSERT(!comparisonFile.isEmpty()); + + drp->fBase.fFilename = baseFile; + drp->fBase.fFullPath = baseFile; + drp->fBase.fStatus = DiffResource::kSpecified_Status; + + drp->fComparison.fFilename = comparisonFile; + drp->fComparison.fFullPath = comparisonFile; + drp->fComparison.fStatus = DiffResource::kSpecified_Status; + + SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str())); + if (NULL != baseFileBits) { + drp->fBase.fStatus = DiffResource::kRead_Status; + } + SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str())); + if (NULL != comparisonFileBits) { + drp->fComparison.fStatus = DiffResource::kRead_Status; + } + if (NULL == baseFileBits || NULL == comparisonFileBits) { + if (NULL == baseFileBits) { + drp->fBase.fStatus = DiffResource::kCouldNotRead_Status; + } + if (NULL == comparisonFileBits) { + drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status; + } + drp->fResult = DiffRecord::kCouldNotCompare_Result; + return; + } + + if (are_buffers_equal(baseFileBits, comparisonFileBits)) { + drp->fResult = DiffRecord::kEqualBits_Result; + return; + } + + get_bitmap(baseFileBits, drp->fBase, SkImageDecoder::kDecodePixels_Mode); + get_bitmap(comparisonFileBits, drp->fComparison, SkImageDecoder::kDecodePixels_Mode); + if (DiffResource::kDecoded_Status != drp->fBase.fStatus || + DiffResource::kDecoded_Status != drp->fComparison.fStatus) + { + drp->fResult = DiffRecord::kCouldNotCompare_Result; + return; + } + + create_and_write_diff_image(drp, dmp, colorThreshold, outputDir, outputFilename); + //TODO: copy fBase.fFilename and fComparison.fFilename to outputDir + // svn and git often present tmp files to diff tools which are promptly deleted + + //TODO: serialize drp to outputDir + // write a tool to deserialize them and call print_diff_page + + SkASSERT(DiffRecord::kUnknown_Result != drp->fResult); +} + +static void usage (char * argv0) { + SkDebugf("Skia image diff tool\n"); + SkDebugf("\n" +"Usage: \n" +" %s <baseFile> <comparisonFile>\n" , argv0); + SkDebugf( +"\nArguments:" +"\n --failonresult <result>: After comparing all file pairs, exit with nonzero" +"\n return code (number of file pairs yielding this" +"\n result) if any file pairs yielded this result." +"\n This flag may be repeated, in which case the" +"\n return code will be the number of fail pairs" +"\n yielding ANY of these results." +"\n --failonstatus <baseStatus> <comparisonStatus>: exit with nonzero return" +"\n code if any file pairs yeilded this status." +"\n --help: display this info" +"\n --listfilenames: list all filenames for each result type in stdout" +"\n --nodiffs: don't write out image diffs, just generate report on stdout" +"\n --outputdir: directory to write difference images" +"\n --threshold <n>: only report differences > n (per color channel) [default 0]" +"\n -u: ignored. Recognized for compatibility with svn diff." +"\n -L: first occurrence label for base, second occurrence label for comparison." +"\n Labels must be of the form \"<filename>(\t<specifier>)?\"." +"\n The base <filename> will be used to create files in outputdir." +"\n" +"\n baseFile: baseline image file." +"\n comparisonFile: comparison image file" +"\n" +"\nIf no sort is specified, it will sort by fraction of pixels mismatching." +"\n"); +} + +const int kNoError = 0; +const int kGenericError = -1; + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + DiffMetricProc diffProc = compute_diff_pmcolor; + + // Maximum error tolerated in any one color channel in any one pixel before + // a difference is reported. + int colorThreshold = 0; + SkString baseFile; + SkString baseLabel; + SkString comparisonFile; + SkString comparisonLabel; + SkString outputDir; + + bool listFilenames = false; + + bool failOnResultType[DiffRecord::kResultCount]; + for (int i = 0; i < DiffRecord::kResultCount; i++) { + failOnResultType[i] = false; + } + + bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount]; + for (int base = 0; base < DiffResource::kStatusCount; ++base) { + for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { + failOnStatusType[base][comparison] = false; + } + } + + int i; + int numUnflaggedArguments = 0; + int numLabelArguments = 0; + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--failonresult")) { + if (argc == ++i) { + SkDebugf("failonresult expects one argument.\n"); + continue; + } + DiffRecord::Result type = DiffRecord::getResultByName(argv[i]); + if (type != DiffRecord::kResultCount) { + failOnResultType[type] = true; + } else { + SkDebugf("ignoring unrecognized result <%s>\n", argv[i]); + } + continue; + } + if (!strcmp(argv[i], "--failonstatus")) { + if (argc == ++i) { + SkDebugf("failonstatus missing base status.\n"); + continue; + } + bool baseStatuses[DiffResource::kStatusCount]; + if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) { + SkDebugf("unrecognized base status <%s>\n", argv[i]); + } + + if (argc == ++i) { + SkDebugf("failonstatus missing comparison status.\n"); + continue; + } + bool comparisonStatuses[DiffResource::kStatusCount]; + if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) { + SkDebugf("unrecognized comarison status <%s>\n", argv[i]); + } + + for (int base = 0; base < DiffResource::kStatusCount; ++base) { + for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { + failOnStatusType[base][comparison] |= + baseStatuses[base] && comparisonStatuses[comparison]; + } + } + continue; + } + if (!strcmp(argv[i], "--help")) { + usage(argv[0]); + return kNoError; + } + if (!strcmp(argv[i], "--listfilenames")) { + listFilenames = true; + continue; + } + if (!strcmp(argv[i], "--outputdir")) { + if (argc == ++i) { + SkDebugf("outputdir expects one argument.\n"); + continue; + } + outputDir.set(argv[i]); + continue; + } + if (!strcmp(argv[i], "--threshold")) { + colorThreshold = atoi(argv[++i]); + continue; + } + if (!strcmp(argv[i], "-u")) { + //we don't produce unified diffs, ignore parameter to work with svn diff + continue; + } + if (!strcmp(argv[i], "-L")) { + if (argc == ++i) { + SkDebugf("label expects one argument.\n"); + continue; + } + switch (numLabelArguments++) { + case 0: + baseLabel.set(argv[i]); + continue; + case 1: + comparisonLabel.set(argv[i]); + continue; + default: + SkDebugf("extra label argument <%s>\n", argv[i]); + usage(argv[0]); + return kGenericError; + } + continue; + } + if (argv[i][0] != '-') { + switch (numUnflaggedArguments++) { + case 0: + baseFile.set(argv[i]); + continue; + case 1: + comparisonFile.set(argv[i]); + continue; + default: + SkDebugf("extra unflagged argument <%s>\n", argv[i]); + usage(argv[0]); + return kGenericError; + } + } + + SkDebugf("Unrecognized argument <%s>\n", argv[i]); + usage(argv[0]); + return kGenericError; + } + + if (numUnflaggedArguments != 2) { + usage(argv[0]); + return kGenericError; + } + + if (listFilenames) { + printf("Base file is [%s]\n", baseFile.c_str()); + } + + if (listFilenames) { + printf("Comparison file is [%s]\n", comparisonFile.c_str()); + } + + if (outputDir.isEmpty()) { + if (listFilenames) { + printf("Not writing any diffs. No output dir specified.\n"); + } + } else { + if (!outputDir.endsWith(PATH_DIV_STR)) { + outputDir.append(PATH_DIV_STR); + } + if (listFilenames) { + printf("Writing diffs. Output dir is [%s]\n", outputDir.c_str()); + } + } + + // Some obscure documentation about diff/patch labels: + // + // Posix says the format is: <filename><tab><date> + // It also states that if a filename contains <tab> or <newline> + // the result is implementation defined + // + // Svn diff --diff-cmd provides labels of the form: <filename><tab><revision> + // + // Git diff --ext-diff does not supply arguments compatible with diff. + // However, it does provide the filename directly. + // skimagediff_git.sh: skimagediff %2 %5 -L "%1\t(%3)" -L "%1\t(%6)" + // + // Git difftool sets $LOCAL, $REMOTE, $MERGED, and $BASE instead of command line parameters. + // difftool.<>.cmd: skimagediff $LOCAL $REMOTE -L "$MERGED\t(local)" -L "$MERGED\t(remote)" + // + // Diff will write any specified label verbatim. Without a specified label diff will write + // <filename><tab><date> + // However, diff will encode the filename as a cstring if the filename contains + // Any of <space> or <double quote> + // A char less than 32 + // Any escapable character \\, \a, \b, \t, \n, \v, \f, \r + // + // Patch decodes: + // If first <non-white-space> is <double quote>, parse filename from cstring. + // If there is a <tab> after the first <non-white-space>, filename is + // [first <non-white-space>, the next run of <white-space> with an embedded <tab>). + // Otherwise the filename is [first <non-space>, the next <white-space>). + // + // The filename /dev/null means the file does not exist (used in adds and deletes). + + // Considering the above, skimagediff will consider the contents of a -L parameter as + // <filename>(\t<specifier>)? + SkString outputFile; + + if (baseLabel.isEmpty()) { + baseLabel.set(baseFile); + outputFile = baseLabel; + } else { + const char* baseLabelCstr = baseLabel.c_str(); + const char* tab = strchr(baseLabelCstr, '\t'); + if (NULL == tab) { + outputFile = baseLabel; + } else { + outputFile.set(baseLabelCstr, tab - baseLabelCstr); + } + } + if (comparisonLabel.isEmpty()) { + comparisonLabel.set(comparisonFile); + } + printf("Base: %s\n", baseLabel.c_str()); + printf("Comparison: %s\n", comparisonLabel.c_str()); + + DiffRecord dr; + create_diff_images(diffProc, colorThreshold, baseFile, comparisonFile, outputDir, outputFile, + &dr); + + if (DiffResource::isStatusFailed(dr.fBase.fStatus)) { + printf("Base %s.\n", DiffResource::getStatusDescription(dr.fBase.fStatus)); + } + if (DiffResource::isStatusFailed(dr.fComparison.fStatus)) { + printf("Comparison %s.\n", DiffResource::getStatusDescription(dr.fComparison.fStatus)); + } + printf("Base and Comparison %s.\n", DiffRecord::getResultDescription(dr.fResult)); + + if (DiffRecord::kDifferentPixels_Result == dr.fResult) { + printf("%.4f%% of pixels differ", 100 * dr.fFractionDifference); + printf(" (%.4f%% weighted)", 100 * dr.fWeightedFraction); + if (dr.fFractionDifference < 0.01) { + printf(" %d pixels", static_cast<int>(dr.fFractionDifference * + dr.fBase.fBitmap.width() * + dr.fBase.fBitmap.height())); + } + + printf("\nAverage color mismatch: "); + printf("%d", static_cast<int>(MAX3(dr.fAverageMismatchR, + dr.fAverageMismatchG, + dr.fAverageMismatchB))); + printf("\nMax color mismatch: "); + printf("%d", MAX3(dr.fMaxMismatchR, + dr.fMaxMismatchG, + dr.fMaxMismatchB)); + printf("\n"); + } + printf("\n"); + + int num_failing_results = 0; + if (failOnResultType[dr.fResult]) { + ++num_failing_results; + } + if (failOnStatusType[dr.fBase.fStatus][dr.fComparison.fStatus]) { + ++num_failing_results; + } + + return num_failing_results; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/skdiff_main.cpp b/chromium/third_party/skia/tools/skdiff_main.cpp new file mode 100644 index 00000000000..ba3221678e5 --- /dev/null +++ b/chromium/third_party/skia/tools/skdiff_main.cpp @@ -0,0 +1,803 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "skdiff.h" +#include "skdiff_html.h" +#include "skdiff_utils.h" +#include "SkBitmap.h" +#include "SkData.h" +#include "SkForceLinking.h" +#include "SkImageDecoder.h" +#include "SkImageEncoder.h" +#include "SkOSFile.h" +#include "SkStream.h" +#include "SkTDArray.h" +#include "SkTemplates.h" +#include "SkTSearch.h" +#include "SkTypes.h" + +__SK_FORCE_IMAGE_DECODER_LINKING; + +/** + * skdiff + * + * Given three directory names, expects to find identically-named files in + * each of the first two; the first are treated as a set of baseline, + * the second a set of variant images, and a diff image is written into the + * third directory for each pair. + * Creates an index.html in the current third directory to compare each + * pair that does not match exactly. + * Recursively descends directories, unless run with --norecurse. + * + * Returns zero exit code if all images match across baseDir and comparisonDir. + */ + +typedef SkTDArray<SkString*> StringArray; +typedef StringArray FileArray; + +struct DiffSummary { + DiffSummary () + : fNumMatches(0) + , fNumMismatches(0) + , fMaxMismatchV(0) + , fMaxMismatchPercent(0) { }; + + ~DiffSummary() { + for (int i = 0; i < DiffRecord::kResultCount; ++i) { + fResultsOfType[i].deleteAll(); + } + for (int base = 0; base < DiffResource::kStatusCount; ++base) { + for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { + fStatusOfType[base][comparison].deleteAll(); + } + } + } + + uint32_t fNumMatches; + uint32_t fNumMismatches; + uint32_t fMaxMismatchV; + float fMaxMismatchPercent; + + FileArray fResultsOfType[DiffRecord::kResultCount]; + FileArray fStatusOfType[DiffResource::kStatusCount][DiffResource::kStatusCount]; + + void printContents(const FileArray& fileArray, + const char* baseStatus, const char* comparisonStatus, + bool listFilenames) { + int n = fileArray.count(); + printf("%d file pairs %s in baseDir and %s in comparisonDir", + n, baseStatus, comparisonStatus); + if (listFilenames) { + printf(": "); + for (int i = 0; i < n; ++i) { + printf("%s ", fileArray[i]->c_str()); + } + } + printf("\n"); + } + + void printStatus(bool listFilenames, + bool failOnStatusType[DiffResource::kStatusCount] + [DiffResource::kStatusCount]) { + typedef DiffResource::Status Status; + + for (int base = 0; base < DiffResource::kStatusCount; ++base) { + Status baseStatus = static_cast<Status>(base); + for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { + Status comparisonStatus = static_cast<Status>(comparison); + const FileArray& fileArray = fStatusOfType[base][comparison]; + if (fileArray.count() > 0) { + if (failOnStatusType[base][comparison]) { + printf(" [*] "); + } else { + printf(" [_] "); + } + printContents(fileArray, + DiffResource::getStatusDescription(baseStatus), + DiffResource::getStatusDescription(comparisonStatus), + listFilenames); + } + } + } + } + + // Print a line about the contents of this FileArray to stdout. + void printContents(const FileArray& fileArray, const char* headerText, bool listFilenames) { + int n = fileArray.count(); + printf("%d file pairs %s", n, headerText); + if (listFilenames) { + printf(": "); + for (int i = 0; i < n; ++i) { + printf("%s ", fileArray[i]->c_str()); + } + } + printf("\n"); + } + + void print(bool listFilenames, bool failOnResultType[DiffRecord::kResultCount], + bool failOnStatusType[DiffResource::kStatusCount] + [DiffResource::kStatusCount]) { + printf("\ncompared %d file pairs:\n", fNumMatches + fNumMismatches); + for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) { + DiffRecord::Result result = static_cast<DiffRecord::Result>(resultInt); + if (failOnResultType[result]) { + printf("[*] "); + } else { + printf("[_] "); + } + printContents(fResultsOfType[result], DiffRecord::getResultDescription(result), + listFilenames); + if (DiffRecord::kCouldNotCompare_Result == result) { + printStatus(listFilenames, failOnStatusType); + } + } + printf("(results marked with [*] will cause nonzero return value)\n"); + printf("\nnumber of mismatching file pairs: %d\n", fNumMismatches); + if (fNumMismatches > 0) { + printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV); + printf("Largest area mismatch was %.2f%% of pixels\n",fMaxMismatchPercent); + } + } + + void add (DiffRecord* drp) { + uint32_t mismatchValue; + + if (drp->fBase.fFilename.equals(drp->fComparison.fFilename)) { + fResultsOfType[drp->fResult].push(new SkString(drp->fBase.fFilename)); + } else { + SkString* blame = new SkString("("); + blame->append(drp->fBase.fFilename); + blame->append(", "); + blame->append(drp->fComparison.fFilename); + blame->append(")"); + fResultsOfType[drp->fResult].push(blame); + } + switch (drp->fResult) { + case DiffRecord::kEqualBits_Result: + fNumMatches++; + break; + case DiffRecord::kEqualPixels_Result: + fNumMatches++; + break; + case DiffRecord::kDifferentSizes_Result: + fNumMismatches++; + break; + case DiffRecord::kDifferentPixels_Result: + fNumMismatches++; + if (drp->fFractionDifference * 100 > fMaxMismatchPercent) { + fMaxMismatchPercent = drp->fFractionDifference * 100; + } + mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG, + drp->fMaxMismatchB); + if (mismatchValue > fMaxMismatchV) { + fMaxMismatchV = mismatchValue; + } + break; + case DiffRecord::kCouldNotCompare_Result: + fNumMismatches++; + fStatusOfType[drp->fBase.fStatus][drp->fComparison.fStatus].push( + new SkString(drp->fBase.fFilename)); + break; + case DiffRecord::kUnknown_Result: + SkDEBUGFAIL("adding uncategorized DiffRecord"); + break; + default: + SkDEBUGFAIL("adding DiffRecord with unhandled fResult value"); + break; + } + } +}; + +/// Returns true if string contains any of these substrings. +static bool string_contains_any_of(const SkString& string, + const StringArray& substrings) { + for (int i = 0; i < substrings.count(); i++) { + if (string.contains(substrings[i]->c_str())) { + return true; + } + } + return false; +} + +/// Internal (potentially recursive) implementation of get_file_list. +static void get_file_list_subdir(const SkString& rootDir, const SkString& subDir, + const StringArray& matchSubstrings, + const StringArray& nomatchSubstrings, + bool recurseIntoSubdirs, FileArray *files) { + bool isSubDirEmpty = subDir.isEmpty(); + SkString dir(rootDir); + if (!isSubDirEmpty) { + dir.append(PATH_DIV_STR); + dir.append(subDir); + } + + // Iterate over files (not directories) within dir. + SkOSFile::Iter fileIterator(dir.c_str()); + SkString fileName; + while (fileIterator.next(&fileName, false)) { + if (fileName.startsWith(".")) { + continue; + } + SkString pathRelativeToRootDir(subDir); + if (!isSubDirEmpty) { + pathRelativeToRootDir.append(PATH_DIV_STR); + } + pathRelativeToRootDir.append(fileName); + if (string_contains_any_of(pathRelativeToRootDir, matchSubstrings) && + !string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) { + files->push(new SkString(pathRelativeToRootDir)); + } + } + + // Recurse into any non-ignored subdirectories. + if (recurseIntoSubdirs) { + SkOSFile::Iter dirIterator(dir.c_str()); + SkString dirName; + while (dirIterator.next(&dirName, true)) { + if (dirName.startsWith(".")) { + continue; + } + SkString pathRelativeToRootDir(subDir); + if (!isSubDirEmpty) { + pathRelativeToRootDir.append(PATH_DIV_STR); + } + pathRelativeToRootDir.append(dirName); + if (!string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) { + get_file_list_subdir(rootDir, pathRelativeToRootDir, + matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, + files); + } + } + } +} + +/// Iterate over dir and get all files whose filename: +/// - matches any of the substrings in matchSubstrings, but... +/// - DOES NOT match any of the substrings in nomatchSubstrings +/// - DOES NOT start with a dot (.) +/// Adds the matching files to the list in *files. +static void get_file_list(const SkString& dir, + const StringArray& matchSubstrings, + const StringArray& nomatchSubstrings, + bool recurseIntoSubdirs, FileArray *files) { + get_file_list_subdir(dir, SkString(""), + matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, + files); +} + +static void release_file_list(FileArray *files) { + files->deleteAll(); +} + +/// Comparison routines for qsort, sort by file names. +static int compare_file_name_metrics(SkString **lhs, SkString **rhs) { + return strcmp((*lhs)->c_str(), (*rhs)->c_str()); +} + +class AutoReleasePixels { +public: + AutoReleasePixels(DiffRecord* drp) + : fDrp(drp) { + SkASSERT(drp != NULL); + } + ~AutoReleasePixels() { + fDrp->fBase.fBitmap.setPixelRef(NULL); + fDrp->fComparison.fBitmap.setPixelRef(NULL); + fDrp->fDifference.fBitmap.setPixelRef(NULL); + fDrp->fWhite.fBitmap.setPixelRef(NULL); + } + +private: + DiffRecord* fDrp; +}; + +static void get_bounds(DiffResource& resource, const char* name) { + if (resource.fBitmap.empty() && !DiffResource::isStatusFailed(resource.fStatus)) { + SkAutoDataUnref fileBits(read_file(resource.fFullPath.c_str())); + if (NULL == fileBits) { + SkDebugf("WARNING: couldn't read %s file <%s>\n", name, resource.fFullPath.c_str()); + resource.fStatus = DiffResource::kCouldNotRead_Status; + } else { + get_bitmap(fileBits, resource, SkImageDecoder::kDecodeBounds_Mode); + } + } +} + +static void get_bounds(DiffRecord& drp) { + get_bounds(drp.fBase, "base"); + get_bounds(drp.fComparison, "comparison"); +} + +#ifdef SK_OS_WIN +#define ANSI_COLOR_RED "" +#define ANSI_COLOR_GREEN "" +#define ANSI_COLOR_YELLOW "" +#define ANSI_COLOR_RESET "" +#else +#define ANSI_COLOR_RED "\x1b[31m" +#define ANSI_COLOR_GREEN "\x1b[32m" +#define ANSI_COLOR_YELLOW "\x1b[33m" +#define ANSI_COLOR_RESET "\x1b[0m" +#endif + +#define VERBOSE_STATUS(status,color,filename) if (verbose) printf( "[ " color " %10s " ANSI_COLOR_RESET " ] %s\n", status, filename->c_str()) + +/// Creates difference images, returns the number that have a 0 metric. +/// If outputDir.isEmpty(), don't write out diff files. +static void create_diff_images (DiffMetricProc dmp, + const int colorThreshold, + RecordArray* differences, + const SkString& baseDir, + const SkString& comparisonDir, + const SkString& outputDir, + const StringArray& matchSubstrings, + const StringArray& nomatchSubstrings, + bool recurseIntoSubdirs, + bool getBounds, + bool verbose, + DiffSummary* summary) { + SkASSERT(!baseDir.isEmpty()); + SkASSERT(!comparisonDir.isEmpty()); + + FileArray baseFiles; + FileArray comparisonFiles; + + get_file_list(baseDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &baseFiles); + get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, + &comparisonFiles); + + if (!baseFiles.isEmpty()) { + qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*), + SkCastForQSort(compare_file_name_metrics)); + } + if (!comparisonFiles.isEmpty()) { + qsort(comparisonFiles.begin(), comparisonFiles.count(), + sizeof(SkString*), SkCastForQSort(compare_file_name_metrics)); + } + + int i = 0; + int j = 0; + + while (i < baseFiles.count() && + j < comparisonFiles.count()) { + + SkString basePath(baseDir); + SkString comparisonPath(comparisonDir); + + DiffRecord *drp = new DiffRecord; + int v = strcmp(baseFiles[i]->c_str(), comparisonFiles[j]->c_str()); + + if (v < 0) { + // in baseDir, but not in comparisonDir + drp->fResult = DiffRecord::kCouldNotCompare_Result; + + basePath.append(*baseFiles[i]); + comparisonPath.append(*baseFiles[i]); + + drp->fBase.fFilename = *baseFiles[i]; + drp->fBase.fFullPath = basePath; + drp->fBase.fStatus = DiffResource::kExists_Status; + + drp->fComparison.fFilename = *baseFiles[i]; + drp->fComparison.fFullPath = comparisonPath; + drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status; + + VERBOSE_STATUS("MISSING", ANSI_COLOR_YELLOW, baseFiles[i]); + + ++i; + } else if (v > 0) { + // in comparisonDir, but not in baseDir + drp->fResult = DiffRecord::kCouldNotCompare_Result; + + basePath.append(*comparisonFiles[j]); + comparisonPath.append(*comparisonFiles[j]); + + drp->fBase.fFilename = *comparisonFiles[j]; + drp->fBase.fFullPath = basePath; + drp->fBase.fStatus = DiffResource::kDoesNotExist_Status; + + drp->fComparison.fFilename = *comparisonFiles[j]; + drp->fComparison.fFullPath = comparisonPath; + drp->fComparison.fStatus = DiffResource::kExists_Status; + + VERBOSE_STATUS("MISSING", ANSI_COLOR_YELLOW, comparisonFiles[j]); + + ++j; + } else { + // Found the same filename in both baseDir and comparisonDir. + SkASSERT(DiffRecord::kUnknown_Result == drp->fResult); + + basePath.append(*baseFiles[i]); + comparisonPath.append(*comparisonFiles[j]); + + drp->fBase.fFilename = *baseFiles[i]; + drp->fBase.fFullPath = basePath; + drp->fBase.fStatus = DiffResource::kExists_Status; + + drp->fComparison.fFilename = *comparisonFiles[j]; + drp->fComparison.fFullPath = comparisonPath; + drp->fComparison.fStatus = DiffResource::kExists_Status; + + SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str())); + if (NULL != baseFileBits) { + drp->fBase.fStatus = DiffResource::kRead_Status; + } + SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str())); + if (NULL != comparisonFileBits) { + drp->fComparison.fStatus = DiffResource::kRead_Status; + } + if (NULL == baseFileBits || NULL == comparisonFileBits) { + if (NULL == baseFileBits) { + drp->fBase.fStatus = DiffResource::kCouldNotRead_Status; + VERBOSE_STATUS("READ FAIL", ANSI_COLOR_RED, baseFiles[i]); + } + if (NULL == comparisonFileBits) { + drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status; + VERBOSE_STATUS("READ FAIL", ANSI_COLOR_RED, comparisonFiles[j]); + } + drp->fResult = DiffRecord::kCouldNotCompare_Result; + + } else if (are_buffers_equal(baseFileBits, comparisonFileBits)) { + drp->fResult = DiffRecord::kEqualBits_Result; + VERBOSE_STATUS("MATCH", ANSI_COLOR_GREEN, baseFiles[i]); + } else { + AutoReleasePixels arp(drp); + get_bitmap(baseFileBits, drp->fBase, SkImageDecoder::kDecodePixels_Mode); + get_bitmap(comparisonFileBits, drp->fComparison, + SkImageDecoder::kDecodePixels_Mode); + VERBOSE_STATUS("DIFFERENT", ANSI_COLOR_RED, baseFiles[i]); + if (DiffResource::kDecoded_Status == drp->fBase.fStatus && + DiffResource::kDecoded_Status == drp->fComparison.fStatus) { + create_and_write_diff_image(drp, dmp, colorThreshold, + outputDir, drp->fBase.fFilename); + } else { + drp->fResult = DiffRecord::kCouldNotCompare_Result; + } + } + + ++i; + ++j; + } + + if (getBounds) { + get_bounds(*drp); + } + SkASSERT(DiffRecord::kUnknown_Result != drp->fResult); + differences->push(drp); + summary->add(drp); + } + + for (; i < baseFiles.count(); ++i) { + // files only in baseDir + DiffRecord *drp = new DiffRecord(); + drp->fBase.fFilename = *baseFiles[i]; + drp->fBase.fFullPath = baseDir; + drp->fBase.fFullPath.append(drp->fBase.fFilename); + drp->fBase.fStatus = DiffResource::kExists_Status; + + drp->fComparison.fFilename = *baseFiles[i]; + drp->fComparison.fFullPath = comparisonDir; + drp->fComparison.fFullPath.append(drp->fComparison.fFilename); + drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status; + + drp->fResult = DiffRecord::kCouldNotCompare_Result; + if (getBounds) { + get_bounds(*drp); + } + differences->push(drp); + summary->add(drp); + } + + for (; j < comparisonFiles.count(); ++j) { + // files only in comparisonDir + DiffRecord *drp = new DiffRecord(); + drp->fBase.fFilename = *comparisonFiles[j]; + drp->fBase.fFullPath = baseDir; + drp->fBase.fFullPath.append(drp->fBase.fFilename); + drp->fBase.fStatus = DiffResource::kDoesNotExist_Status; + + drp->fComparison.fFilename = *comparisonFiles[j]; + drp->fComparison.fFullPath = comparisonDir; + drp->fComparison.fFullPath.append(drp->fComparison.fFilename); + drp->fComparison.fStatus = DiffResource::kExists_Status; + + drp->fResult = DiffRecord::kCouldNotCompare_Result; + if (getBounds) { + get_bounds(*drp); + } + differences->push(drp); + summary->add(drp); + } + + release_file_list(&baseFiles); + release_file_list(&comparisonFiles); +} + +static void usage (char * argv0) { + SkDebugf("Skia baseline image diff tool\n"); + SkDebugf("\n" +"Usage: \n" +" %s <baseDir> <comparisonDir> [outputDir] \n", argv0); + SkDebugf( +"\nArguments:" +"\n --failonresult <result>: After comparing all file pairs, exit with nonzero" +"\n return code (number of file pairs yielding this" +"\n result) if any file pairs yielded this result." +"\n This flag may be repeated, in which case the" +"\n return code will be the number of fail pairs" +"\n yielding ANY of these results." +"\n --failonstatus <baseStatus> <comparisonStatus>: exit with nonzero return" +"\n code if any file pairs yielded this status." +"\n --help: display this info" +"\n --listfilenames: list all filenames for each result type in stdout" +"\n --match <substring>: compare files whose filenames contain this substring;" +"\n if unspecified, compare ALL files." +"\n this flag may be repeated." +"\n --nodiffs: don't write out image diffs or index.html, just generate" +"\n report on stdout" +"\n --nomatch <substring>: regardless of --match, DO NOT compare files whose" +"\n filenames contain this substring." +"\n this flag may be repeated." +"\n --noprintdirs: do not print the directories used." +"\n --norecurse: do not recurse into subdirectories." +"\n --sortbymaxmismatch: sort by worst color channel mismatch;" +"\n break ties with -sortbymismatch" +"\n --sortbymismatch: sort by average color channel mismatch" +"\n --threshold <n>: only report differences > n (per color channel) [default 0]" +"\n --weighted: sort by # pixels different weighted by color difference" +"\n" +"\n baseDir: directory to read baseline images from." +"\n comparisonDir: directory to read comparison images from" +"\n outputDir: directory to write difference images and index.html to;" +"\n defaults to comparisonDir" +"\n" +"\nIf no sort is specified, it will sort by fraction of pixels mismatching." +"\n"); +} + +const int kNoError = 0; +const int kGenericError = -1; + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + DiffMetricProc diffProc = compute_diff_pmcolor; + int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>; + + // Maximum error tolerated in any one color channel in any one pixel before + // a difference is reported. + int colorThreshold = 0; + SkString baseDir; + SkString comparisonDir; + SkString outputDir; + + StringArray matchSubstrings; + StringArray nomatchSubstrings; + + bool generateDiffs = true; + bool listFilenames = false; + bool printDirNames = true; + bool recurseIntoSubdirs = true; + bool verbose = false; + + RecordArray differences; + DiffSummary summary; + + bool failOnResultType[DiffRecord::kResultCount]; + for (int i = 0; i < DiffRecord::kResultCount; i++) { + failOnResultType[i] = false; + } + + bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount]; + for (int base = 0; base < DiffResource::kStatusCount; ++base) { + for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { + failOnStatusType[base][comparison] = false; + } + } + + int i; + int numUnflaggedArguments = 0; + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--failonresult")) { + if (argc == ++i) { + SkDebugf("failonresult expects one argument.\n"); + continue; + } + DiffRecord::Result type = DiffRecord::getResultByName(argv[i]); + if (type != DiffRecord::kResultCount) { + failOnResultType[type] = true; + } else { + SkDebugf("ignoring unrecognized result <%s>\n", argv[i]); + } + continue; + } + if (!strcmp(argv[i], "--failonstatus")) { + if (argc == ++i) { + SkDebugf("failonstatus missing base status.\n"); + continue; + } + bool baseStatuses[DiffResource::kStatusCount]; + if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) { + SkDebugf("unrecognized base status <%s>\n", argv[i]); + } + + if (argc == ++i) { + SkDebugf("failonstatus missing comparison status.\n"); + continue; + } + bool comparisonStatuses[DiffResource::kStatusCount]; + if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) { + SkDebugf("unrecognized comarison status <%s>\n", argv[i]); + } + + for (int base = 0; base < DiffResource::kStatusCount; ++base) { + for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { + failOnStatusType[base][comparison] |= + baseStatuses[base] && comparisonStatuses[comparison]; + } + } + continue; + } + if (!strcmp(argv[i], "--help")) { + usage(argv[0]); + return kNoError; + } + if (!strcmp(argv[i], "--listfilenames")) { + listFilenames = true; + continue; + } + if (!strcmp(argv[i], "--verbose")) { + verbose = true; + continue; + } + if (!strcmp(argv[i], "--match")) { + matchSubstrings.push(new SkString(argv[++i])); + continue; + } + if (!strcmp(argv[i], "--nodiffs")) { + generateDiffs = false; + continue; + } + if (!strcmp(argv[i], "--nomatch")) { + nomatchSubstrings.push(new SkString(argv[++i])); + continue; + } + if (!strcmp(argv[i], "--noprintdirs")) { + printDirNames = false; + continue; + } + if (!strcmp(argv[i], "--norecurse")) { + recurseIntoSubdirs = false; + continue; + } + if (!strcmp(argv[i], "--sortbymaxmismatch")) { + sortProc = compare<CompareDiffMaxMismatches>; + continue; + } + if (!strcmp(argv[i], "--sortbymismatch")) { + sortProc = compare<CompareDiffMeanMismatches>; + continue; + } + if (!strcmp(argv[i], "--threshold")) { + colorThreshold = atoi(argv[++i]); + continue; + } + if (!strcmp(argv[i], "--weighted")) { + sortProc = compare<CompareDiffWeighted>; + continue; + } + if (argv[i][0] != '-') { + switch (numUnflaggedArguments++) { + case 0: + baseDir.set(argv[i]); + continue; + case 1: + comparisonDir.set(argv[i]); + continue; + case 2: + outputDir.set(argv[i]); + continue; + default: + SkDebugf("extra unflagged argument <%s>\n", argv[i]); + usage(argv[0]); + return kGenericError; + } + } + + SkDebugf("Unrecognized argument <%s>\n", argv[i]); + usage(argv[0]); + return kGenericError; + } + + if (numUnflaggedArguments == 2) { + outputDir = comparisonDir; + } else if (numUnflaggedArguments != 3) { + usage(argv[0]); + return kGenericError; + } + + if (!baseDir.endsWith(PATH_DIV_STR)) { + baseDir.append(PATH_DIV_STR); + } + if (printDirNames) { + printf("baseDir is [%s]\n", baseDir.c_str()); + } + + if (!comparisonDir.endsWith(PATH_DIV_STR)) { + comparisonDir.append(PATH_DIV_STR); + } + if (printDirNames) { + printf("comparisonDir is [%s]\n", comparisonDir.c_str()); + } + + if (!outputDir.endsWith(PATH_DIV_STR)) { + outputDir.append(PATH_DIV_STR); + } + if (generateDiffs) { + if (printDirNames) { + printf("writing diffs to outputDir is [%s]\n", outputDir.c_str()); + } + } else { + if (printDirNames) { + printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str()); + } + outputDir.set(""); + } + + // If no matchSubstrings were specified, match ALL strings + // (except for whatever nomatchSubstrings were specified, if any). + if (matchSubstrings.isEmpty()) { + matchSubstrings.push(new SkString("")); + } + + create_diff_images(diffProc, colorThreshold, &differences, + baseDir, comparisonDir, outputDir, + matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, generateDiffs, + verbose, &summary); + summary.print(listFilenames, failOnResultType, failOnStatusType); + + if (differences.count()) { + qsort(differences.begin(), differences.count(), + sizeof(DiffRecord*), sortProc); + } + + if (generateDiffs) { + print_diff_page(summary.fNumMatches, colorThreshold, differences, + baseDir, comparisonDir, outputDir); + } + + for (i = 0; i < differences.count(); i++) { + delete differences[i]; + } + matchSubstrings.deleteAll(); + nomatchSubstrings.deleteAll(); + + int num_failing_results = 0; + for (int i = 0; i < DiffRecord::kResultCount; i++) { + if (failOnResultType[i]) { + num_failing_results += summary.fResultsOfType[i].count(); + } + } + if (!failOnResultType[DiffRecord::kCouldNotCompare_Result]) { + for (int base = 0; base < DiffResource::kStatusCount; ++base) { + for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { + if (failOnStatusType[base][comparison]) { + num_failing_results += summary.fStatusOfType[base][comparison].count(); + } + } + } + } + + // On Linux (and maybe other platforms too), any results outside of the + // range [0...255] are wrapped (mod 256). Do the conversion ourselves, to + // make sure that we only return 0 when there were no failures. + return (num_failing_results > 255) ? 255 : num_failing_results; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/skdiff_utils.cpp b/chromium/third_party/skia/tools/skdiff_utils.cpp new file mode 100644 index 00000000000..9157ac6b918 --- /dev/null +++ b/chromium/third_party/skia/tools/skdiff_utils.cpp @@ -0,0 +1,184 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "skdiff.h" +#include "skdiff_utils.h" +#include "SkBitmap.h" +#include "SkData.h" +#include "SkImageDecoder.h" +#include "SkImageEncoder.h" +#include "SkStream.h" +#include "SkTemplates.h" +#include "SkTypes.h" + +bool are_buffers_equal(SkData* skdata1, SkData* skdata2) { + if ((NULL == skdata1) || (NULL == skdata2)) { + return false; + } + if (skdata1->size() != skdata2->size()) { + return false; + } + return (0 == memcmp(skdata1->data(), skdata2->data(), skdata1->size())); +} + +SkData* read_file(const char* file_path) { + SkFILEStream fileStream(file_path); + if (!fileStream.isValid()) { + SkDebugf("WARNING: could not open file <%s> for reading\n", file_path); + return NULL; + } + size_t bytesInFile = fileStream.getLength(); + size_t bytesLeftToRead = bytesInFile; + + void* bufferStart = sk_malloc_throw(bytesInFile); + char* bufferPointer = (char*)bufferStart; + while (bytesLeftToRead > 0) { + size_t bytesReadThisTime = fileStream.read(bufferPointer, bytesLeftToRead); + if (0 == bytesReadThisTime) { + SkDebugf("WARNING: error reading from <%s>\n", file_path); + sk_free(bufferStart); + return NULL; + } + bytesLeftToRead -= bytesReadThisTime; + bufferPointer += bytesReadThisTime; + } + return SkData::NewFromMalloc(bufferStart, bytesInFile); +} + +bool get_bitmap(SkData* fileBits, DiffResource& resource, SkImageDecoder::Mode mode) { + SkMemoryStream stream(fileBits->data(), fileBits->size()); + + SkImageDecoder* codec = SkImageDecoder::Factory(&stream); + if (NULL == codec) { + SkDebugf("ERROR: no codec found for <%s>\n", resource.fFullPath.c_str()); + resource.fStatus = DiffResource::kCouldNotDecode_Status; + return false; + } + + // In debug, the DLL will automatically be unloaded when this is deleted, + // but that shouldn't be a problem in release mode. + SkAutoTDelete<SkImageDecoder> ad(codec); + + stream.rewind(); + if (!codec->decode(&stream, &resource.fBitmap, kN32_SkColorType, mode)) { + SkDebugf("ERROR: codec failed for basePath <%s>\n", resource.fFullPath.c_str()); + resource.fStatus = DiffResource::kCouldNotDecode_Status; + return false; + } + + resource.fStatus = DiffResource::kDecoded_Status; + return true; +} + +/** Thanks to PNG, we need to force all pixels 100% opaque. */ +static void force_all_opaque(const SkBitmap& bitmap) { + SkAutoLockPixels lock(bitmap); + for (int y = 0; y < bitmap.height(); y++) { + for (int x = 0; x < bitmap.width(); x++) { + *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT); + } + } +} + +bool write_bitmap(const SkString& path, const SkBitmap& bitmap) { + SkBitmap copy; + bitmap.copyTo(©, kN32_SkColorType); + force_all_opaque(copy); + return SkImageEncoder::EncodeFile(path.c_str(), copy, + SkImageEncoder::kPNG_Type, 100); +} + +/// Return a copy of the "input" string, within which we have replaced all instances +/// of oldSubstring with newSubstring. +/// +/// TODO: If we like this, we should move it into the core SkString implementation, +/// adding more checks and ample test cases, and paying more attention to efficiency. +static SkString replace_all(const SkString &input, + const char oldSubstring[], const char newSubstring[]) { + SkString output; + const char *input_cstr = input.c_str(); + const char *first_char = input_cstr; + const char *match_char; + size_t oldSubstringLen = strlen(oldSubstring); + while (NULL != (match_char = strstr(first_char, oldSubstring))) { + output.append(first_char, (match_char - first_char)); + output.append(newSubstring); + first_char = match_char + oldSubstringLen; + } + output.append(first_char); + return output; +} + +static SkString filename_to_derived_filename(const SkString& filename, const char *suffix) { + SkString diffName (filename); + const char* cstring = diffName.c_str(); + size_t dotOffset = strrchr(cstring, '.') - cstring; + diffName.remove(dotOffset, diffName.size() - dotOffset); + diffName.append(suffix); + + // In case we recursed into subdirectories, replace slashes with something else + // so the diffs will all be written into a single flat directory. + diffName = replace_all(diffName, PATH_DIV_STR, "_"); + return diffName; +} + +SkString filename_to_diff_filename(const SkString& filename) { + return filename_to_derived_filename(filename, "-diff.png"); +} + +SkString filename_to_white_filename(const SkString& filename) { + return filename_to_derived_filename(filename, "-white.png"); +} + +void create_and_write_diff_image(DiffRecord* drp, + DiffMetricProc dmp, + const int colorThreshold, + const SkString& outputDir, + const SkString& filename) { + const int w = drp->fBase.fBitmap.width(); + const int h = drp->fBase.fBitmap.height(); + + if (w != drp->fComparison.fBitmap.width() || h != drp->fComparison.fBitmap.height()) { + drp->fResult = DiffRecord::kDifferentSizes_Result; + } else { + drp->fDifference.fBitmap.allocN32Pixels(w, h); + + drp->fWhite.fBitmap.allocN32Pixels(w, h); + + SkASSERT(DiffRecord::kUnknown_Result == drp->fResult); + compute_diff(drp, dmp, colorThreshold); + SkASSERT(DiffRecord::kUnknown_Result != drp->fResult); + } + + if (outputDir.isEmpty()) { + drp->fDifference.fStatus = DiffResource::kUnspecified_Status; + drp->fWhite.fStatus = DiffResource::kUnspecified_Status; + + } else { + drp->fDifference.fFilename = filename_to_diff_filename(filename); + drp->fDifference.fFullPath = outputDir; + drp->fDifference.fFullPath.append(drp->fDifference.fFilename); + drp->fDifference.fStatus = DiffResource::kSpecified_Status; + + drp->fWhite.fFilename = filename_to_white_filename(filename); + drp->fWhite.fFullPath = outputDir; + drp->fWhite.fFullPath.append(drp->fWhite.fFilename); + drp->fWhite.fStatus = DiffResource::kSpecified_Status; + + if (DiffRecord::kDifferentPixels_Result == drp->fResult) { + if (write_bitmap(drp->fDifference.fFullPath, drp->fDifference.fBitmap)) { + drp->fDifference.fStatus = DiffResource::kExists_Status; + } else { + drp->fDifference.fStatus = DiffResource::kDoesNotExist_Status; + } + if (write_bitmap(drp->fWhite.fFullPath, drp->fWhite.fBitmap)) { + drp->fWhite.fStatus = DiffResource::kExists_Status; + } else { + drp->fWhite.fStatus = DiffResource::kDoesNotExist_Status; + } + } + } +} diff --git a/chromium/third_party/skia/tools/skdiff_utils.h b/chromium/third_party/skia/tools/skdiff_utils.h new file mode 100644 index 00000000000..00ebf899a95 --- /dev/null +++ b/chromium/third_party/skia/tools/skdiff_utils.h @@ -0,0 +1,53 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef skdiff_utils_DEFINED +#define skdiff_utils_DEFINED + +#include "skdiff.h" +#include "SkImageDecoder.h" + +class SkBitmap; +class SkData; +class SkString; + +/** Returns true if the two buffers passed in are both non-NULL, + * have the same length, and contain exactly the same byte values. + */ +bool are_buffers_equal(SkData* skdata1, SkData* skdata2); + +/** Reads the file at the given path and returns its complete contents as an + * SkData object (or returns NULL on error). + */ +SkData* read_file(const char* file_path); + +/** Decodes the fileBits into the resource.fBitmap. Returns false on failure. */ +bool get_bitmap(SkData* fileBits, DiffResource& resource, SkImageDecoder::Mode mode); + +/** Writes the bitmap as a PNG to the path specified. */ +bool write_bitmap(const SkString& path, const SkBitmap& bitmap); + +/** Given an image filename, returns the name of the file containing + * the associated difference image. + */ +SkString filename_to_diff_filename(const SkString& filename); + +/** Given an image filename, returns the name of the file containing + * the "white" difference image. + */ +SkString filename_to_white_filename(const SkString& filename); + +/** Calls compute_diff and handles the difference and white diff resources. + * If !outputDir.isEmpty(), writes out difference and white images. + */ +void create_and_write_diff_image(DiffRecord* drp, + DiffMetricProc dmp, + const int colorThreshold, + const SkString& outputDir, + const SkString& filename); + +#endif diff --git a/chromium/third_party/skia/tools/skhello.cpp b/chromium/third_party/skia/tools/skhello.cpp new file mode 100644 index 00000000000..55748d2e1bb --- /dev/null +++ b/chromium/third_party/skia/tools/skhello.cpp @@ -0,0 +1,110 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkCanvas.h" +#include "SkCommandLineFlags.h" +#include "SkData.h" +#include "SkDocument.h" +#include "SkForceLinking.h" +#include "SkGraphics.h" +#include "SkSurface.h" +#include "SkImage.h" +#include "SkStream.h" +#include "SkString.h" + +__SK_FORCE_IMAGE_DECODER_LINKING; + +DEFINE_string2(outFile, o, "skhello", "The filename to write the image."); +DEFINE_string2(text, t, "Hello", "The string to write."); + +static void doDraw(SkCanvas* canvas, const SkPaint& paint, const char text[]) { + SkRect bounds; + canvas->getClipBounds(&bounds); + + canvas->drawColor(SK_ColorWHITE); + canvas->drawText(text, strlen(text), + bounds.centerX(), bounds.centerY(), + paint); +} + +static bool do_surface(int w, int h, const char path[], const char text[], + const SkPaint& paint) { + SkAutoTUnref<SkSurface> surface(SkSurface::NewRasterPMColor(w, h)); + doDraw(surface->getCanvas(), paint, text); + + SkAutoTUnref<SkImage> image(surface->newImageSnapshot()); + SkAutoDataUnref data(image->encode()); + if (NULL == data.get()) { + return false; + } + SkFILEWStream stream(path); + return stream.write(data->data(), data->size()); +} + +static bool do_document(int w, int h, const char path[], const char text[], + const SkPaint& paint) { + SkAutoTUnref<SkDocument> doc(SkDocument::CreatePDF(path)); + if (doc.get()) { + SkScalar width = SkIntToScalar(w); + SkScalar height = SkIntToScalar(h); + doDraw(doc->beginPage(width, height, NULL), paint, text); + return true; + } + return false; +} + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + SkCommandLineFlags::SetUsage(""); + SkCommandLineFlags::Parse(argc, argv); + + SkAutoGraphics ag; + SkString path("skhello"); + SkString text("Hello"); + + if (!FLAGS_outFile.isEmpty()) { + path.set(FLAGS_outFile[0]); + } + if (!FLAGS_text.isEmpty()) { + text.set(FLAGS_text[0]); + } + + SkPaint paint; + paint.setAntiAlias(true); + paint.setTextSize(SkIntToScalar(30)); + paint.setTextAlign(SkPaint::kCenter_Align); + + SkScalar width = paint.measureText(text.c_str(), text.size()); + SkScalar spacing = paint.getFontSpacing(); + + int w = SkScalarRoundToInt(width) + 30; + int h = SkScalarRoundToInt(spacing) + 30; + + static const struct { + bool (*fProc)(int w, int h, const char path[], const char text[], + const SkPaint&); + const char* fSuffix; + } gRec[] = { + { do_surface, ".png" }, + { do_document, ".pdf" }, + }; + + for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) { + SkString file; + file.printf("%s%s", path.c_str(), gRec[i].fSuffix); + if (!gRec[i].fProc(w, h, file.c_str(), text.c_str(), paint)) { + return -1; + } + } + return 0; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/skimage_main.cpp b/chromium/third_party/skia/tools/skimage_main.cpp new file mode 100644 index 00000000000..a488aa7e2f0 --- /dev/null +++ b/chromium/third_party/skia/tools/skimage_main.cpp @@ -0,0 +1,842 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "gm_expectations.h" +#include "SkBitmap.h" +#include "SkColorPriv.h" +#include "SkCommandLineFlags.h" +#include "SkData.h" +#include "SkForceLinking.h" +#include "SkGraphics.h" +#include "SkImageDecoder.h" +#include "SkImageEncoder.h" +#include "SkOSFile.h" +#include "SkRandom.h" +#include "SkStream.h" +#include "SkTArray.h" +#include "SkTemplates.h" + +__SK_FORCE_IMAGE_DECODER_LINKING; + +DEFINE_string(config, "None", "Preferred config to decode into. [None|8888|565|A8]"); +DEFINE_string(createExpectationsPath, "", "Path to write JSON expectations."); +DEFINE_string(mismatchPath, "", "Folder to write mismatched images to."); +DEFINE_string2(readPath, r, "", "Folder(s) and files to decode images. Required."); +DEFINE_string(readExpectationsPath, "", "Path to read JSON expectations from."); +DEFINE_bool(reencode, true, "Reencode the images to test encoding."); +DEFINE_int32(sampleSize, 1, "Set the sampleSize for decoding."); +DEFINE_bool(skip, false, "Skip writing zeroes."); +DEFINE_bool(testSubsetDecoding, true, "Test decoding subsets of images."); +DEFINE_bool(writeChecksumBasedFilenames, false, "When writing out actual images, use checksum-" + "based filenames, as rebaseline.py will use when downloading them from Google Storage"); +DEFINE_string2(writePath, w, "", "Write rendered images into this directory."); + +struct Format { + SkImageEncoder::Type fType; + SkImageDecoder::Format fFormat; + const char* fSuffix; +}; + +static const Format gFormats[] = { + { SkImageEncoder::kBMP_Type, SkImageDecoder::kBMP_Format, ".bmp" }, + { SkImageEncoder::kGIF_Type, SkImageDecoder::kGIF_Format, ".gif" }, + { SkImageEncoder::kICO_Type, SkImageDecoder::kICO_Format, ".ico" }, + { SkImageEncoder::kJPEG_Type, SkImageDecoder::kJPEG_Format, ".jpg" }, + { SkImageEncoder::kPNG_Type, SkImageDecoder::kPNG_Format, ".png" }, + { SkImageEncoder::kWBMP_Type, SkImageDecoder::kWBMP_Format, ".wbmp" }, + { SkImageEncoder::kWEBP_Type, SkImageDecoder::kWEBP_Format, ".webp" } +}; + +static SkImageEncoder::Type format_to_type(SkImageDecoder::Format format) { + for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) { + if (gFormats[i].fFormat == format) { + return gFormats[i].fType; + } + } + return SkImageEncoder::kUnknown_Type; +} + +static const char* suffix_for_type(SkImageEncoder::Type type) { + for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) { + if (gFormats[i].fType == type) { + return gFormats[i].fSuffix; + } + } + return ""; +} + +static SkImageDecoder::Format guess_format_from_suffix(const char suffix[]) { + for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) { + if (strcmp(suffix, gFormats[i].fSuffix) == 0) { + return gFormats[i].fFormat; + } + } + return SkImageDecoder::kUnknown_Format; +} + +static void make_outname(SkString* dst, const char outDir[], const char src[], + const char suffix[]) { + SkString basename = SkOSPath::SkBasename(src); + dst->set(SkOSPath::SkPathJoin(outDir, basename.c_str())); + dst->append(suffix); +} + +// Store the names of the filenames to report later which ones failed, succeeded, and were +// invalid. +// FIXME: Add more arrays, for more specific types of errors, and make the output simpler. +// If each array holds one type of error, the output can change from: +// +// Failures: +// <image> failed for such and such reason +// <image> failed for some different reason +// +// to: +// +// Such and such failures: +// <image> +// +// Different reason failures: +// <image> +// +static SkTArray<SkString, false> gInvalidStreams; +static SkTArray<SkString, false> gMissingCodecs; +static SkTArray<SkString, false> gDecodeFailures; +static SkTArray<SkString, false> gEncodeFailures; +static SkTArray<SkString, false> gSuccessfulDecodes; +static SkTArray<SkString, false> gSuccessfulSubsetDecodes; +static SkTArray<SkString, false> gFailedSubsetDecodes; +// Files/subsets that do not have expectations. Not reported as a failure of the test so +// the bots will not turn red with each new image test. +static SkTArray<SkString, false> gMissingExpectations; +static SkTArray<SkString, false> gMissingSubsetExpectations; +// For files that are expected to fail. +static SkTArray<SkString, false> gKnownFailures; +static SkTArray<SkString, false> gKnownSubsetFailures; + +static SkColorType gPrefColorType(kUnknown_SkColorType); + +// Expections read from a file specified by readExpectationsPath. The expectations must have been +// previously written using createExpectationsPath. +SkAutoTUnref<skiagm::JsonExpectationsSource> gJsonExpectations; + +/** + * Encode the bitmap to a file, written one of two ways, depending on + * FLAGS_writeChecksumBasedFilenames. If true, the final image will be + * written to: + * outDir/hashType/src/digestValue.png + * If false, the final image will be written out to: + * outDir/src.png + * The function returns whether the file was successfully written. + */ +static bool write_bitmap(const char outDir[], const char src[], + const skiagm::BitmapAndDigest& bitmapAndDigest) { + SkString filename; + if (FLAGS_writeChecksumBasedFilenames) { + // First create the directory for the hashtype. + const SkString hashType = bitmapAndDigest.fDigest.getHashType(); + const SkString hashDir = SkOSPath::SkPathJoin(outDir, hashType.c_str()); + if (!sk_mkdir(hashDir.c_str())) { + return false; + } + + // Now create the name of the folder specific to this image. + SkString basename = SkOSPath::SkBasename(src); + const SkString imageDir = SkOSPath::SkPathJoin(hashDir.c_str(), basename.c_str()); + if (!sk_mkdir(imageDir.c_str())) { + return false; + } + + // Name the file <digest>.png + SkString checksumBasedName = bitmapAndDigest.fDigest.getDigestValue(); + checksumBasedName.append(".png"); + + filename = SkOSPath::SkPathJoin(imageDir.c_str(), checksumBasedName.c_str()); + } else { + make_outname(&filename, outDir, src, ".png"); + } + + const SkBitmap& bm = bitmapAndDigest.fBitmap; + if (SkImageEncoder::EncodeFile(filename.c_str(), bm, SkImageEncoder::kPNG_Type, 100)) { + return true; + } + + if (bm.colorType() == kN32_SkColorType) { + // First attempt at encoding failed, and the bitmap was already 8888. Making + // a copy is not going to help. + return false; + } + + // Encoding failed. Copy to 8888 and try again. + SkBitmap bm8888; + if (!bm.copyTo(&bm8888, kN32_SkColorType)) { + return false; + } + return SkImageEncoder::EncodeFile(filename.c_str(), bm8888, SkImageEncoder::kPNG_Type, 100); +} + +/** + * Return a random SkIRect inside the range specified. + * @param rand Random number generator. + * @param maxX Exclusive maximum x-coordinate. SkIRect's fLeft and fRight will be + * in the range [0, maxX) + * @param maxY Exclusive maximum y-coordinate. SkIRect's fTop and fBottom will be + * in the range [0, maxY) + * @return SkIRect Non-empty, non-degenerate rectangle. + */ +static SkIRect generate_random_rect(SkRandom* rand, int32_t maxX, int32_t maxY) { + SkASSERT(maxX > 1 && maxY > 1); + int32_t left = rand->nextULessThan(maxX); + int32_t right = rand->nextULessThan(maxX); + int32_t top = rand->nextULessThan(maxY); + int32_t bottom = rand->nextULessThan(maxY); + SkIRect rect = SkIRect::MakeLTRB(left, top, right, bottom); + rect.sort(); + // Make sure rect is not empty. + if (rect.fLeft == rect.fRight) { + if (rect.fLeft > 0) { + rect.fLeft--; + } else { + rect.fRight++; + // This branch is only taken if 0 == rect.fRight, and + // maxX must be at least 2, so it must still be in + // range. + SkASSERT(rect.fRight < maxX); + } + } + if (rect.fTop == rect.fBottom) { + if (rect.fTop > 0) { + rect.fTop--; + } else { + rect.fBottom++; + // Again, this must be in range. + SkASSERT(rect.fBottom < maxY); + } + } + return rect; +} + +/** + * Return a string which includes the name of the file and the preferred config, + * as specified by "--config". The resulting string will match the pattern of + * gm_json.py's IMAGE_FILENAME_PATTERN: "filename_config.png" + */ +static SkString create_json_key(const char* filename) { + SkASSERT(FLAGS_config.count() == 1); + return SkStringPrintf("%s_%s.png", filename, FLAGS_config[0]); +} + +// Stored expectations to be written to a file if createExpectationsPath is specified. +static Json::Value gExpectationsToWrite; + +/** + * If expectations are to be recorded, record the bitmap expectations into the global + * expectations array. + * As is the case with reading expectations, the key used will combine the filename + * parameter with the preferred config, as specified by "--config", matching the + * pattern of gm_json.py's IMAGE_FILENAME_PATTERN: "filename_config.png" + */ +static void write_expectations(const skiagm::BitmapAndDigest& bitmapAndDigest, + const char* filename) { + const SkString name_config = create_json_key(filename); + if (!FLAGS_createExpectationsPath.isEmpty()) { + // Creates an Expectations object, and add it to the list to write. + skiagm::Expectations expectation(bitmapAndDigest); + Json::Value value = expectation.asJsonValue(); + gExpectationsToWrite[name_config.c_str()] = value; + } +} + +/** + * If --readExpectationsPath is set, compare this bitmap to the json expectations + * provided. + * + * @param digest GmResultDigest, computed from the decoded bitmap, to compare to + * the existing expectation. + * @param filename String used to find the expected value. Will be combined with the + * preferred config, as specified by "--config", to match the pattern of + * gm_json.py's IMAGE_FILENAME_PATTERN: "filename_config.png". The resulting + * key will be used to find the proper expectations. + * @param failureArray Array to add a failure message to on failure. + * @param missingArray Array to add failure message to when missing image + * expectation. + * @param ignoreArray Array to add failure message to when the image does not match + * the expectation, but this is a failure we can ignore. + * @return bool True in any of these cases: + * - the bitmap matches the expectation. + * False in any of these cases: + * - there is no expectations file. + * - there is an expectations file, but no expectation for this bitmap. + * - there is an expectation for this bitmap, but it did not match. + * - expectation could not be computed from the bitmap. + */ +static bool compare_to_expectations_if_necessary(const skiagm::GmResultDigest& digest, + const char* filename, + SkTArray<SkString, false>* failureArray, + SkTArray<SkString, false>* missingArray, + SkTArray<SkString, false>* ignoreArray) { + // For both writing and reading, the key for this entry will include the name + // of the file and the pref config, matching the pattern of gm_json.py's + // IMAGE_FILENAME_PATTERN: "name_config.png" + const SkString name_config = create_json_key(filename); + + if (!digest.isValid()) { + if (failureArray != NULL) { + failureArray->push_back().printf("decoded %s, but could not create a GmResultDigest.", + filename); + } + return false; + } + + if (NULL == gJsonExpectations.get()) { + return false; + } + + skiagm::Expectations jsExpectation = gJsonExpectations->get(name_config.c_str()); + if (jsExpectation.empty()) { + if (missingArray != NULL) { + missingArray->push_back().printf("decoded %s, but could not find expectation.", + filename); + } + return false; + } + + if (jsExpectation.match(digest)) { + return true; + } + + if (jsExpectation.ignoreFailure()) { + ignoreArray->push_back().printf("%s does not match expectation, but this is known.", + filename); + } else if (failureArray != NULL) { + failureArray->push_back().printf("decoded %s, but the result does not match " + "expectations.", + filename); + } + return false; +} + +/** + * Helper function to write a bitmap subset to a file. Only called if subsets were created + * and a writePath was provided. Behaves differently depending on + * FLAGS_writeChecksumBasedFilenames. If true: + * Writes the image to a PNG file named according to the digest hash, as described in + * write_bitmap. + * If false: + * Creates a subdirectory called 'subsets' and writes a PNG to that directory. Also + * creates a subdirectory called 'extracted' and writes a bitmap created using + * extractSubset to a PNG in that directory. Both files will represent the same + * subrectangle and have the same name for convenient comparison. In this case, the + * digest is ignored. + * + * @param writePath Parent directory to hold the folders for the PNG files to write. Must + * not be NULL. + * @param subsetName Basename of the original file, with the dimensions of the subset tacked + * on. Used to name the new file/folder. + * @param bitmapAndDigestFromDecodeSubset SkBitmap (with digest) created by + * SkImageDecoder::DecodeSubset, using rect as the area to decode. + * @param rect Rectangle of the area decoded into bitmapFromDecodeSubset. Used to call + * extractSubset on originalBitmap to create a bitmap with the same dimensions/pixels as + * bitmapFromDecodeSubset (assuming decodeSubset worked properly). + * @param originalBitmap SkBitmap decoded from the same stream as bitmapFromDecodeSubset, + * using SkImageDecoder::decode to get the entire image. Used to create a PNG file for + * comparison to the PNG created by bitmapAndDigestFromDecodeSubset's bitmap. + * @return bool Whether the function succeeded at drawing the decoded subset and the extracted + * subset to files. + */ +static bool write_subset(const char* writePath, const SkString& subsetName, + const skiagm::BitmapAndDigest bitmapAndDigestFromDecodeSubset, + SkIRect rect, const SkBitmap& originalBitmap) { + // All parameters must be valid. + SkASSERT(writePath != NULL); + + SkString subsetPath; + if (FLAGS_writeChecksumBasedFilenames) { + subsetPath.set(writePath); + } else { + // Create a subdirectory to hold the results of decodeSubset. + subsetPath = SkOSPath::SkPathJoin(writePath, "subsets"); + if (!sk_mkdir(subsetPath.c_str())) { + gFailedSubsetDecodes.push_back().printf("Successfully decoded subset %s, but " + "failed to create a directory to write to.", + subsetName.c_str()); + return false; + } + } + SkAssertResult(write_bitmap(subsetPath.c_str(), subsetName.c_str(), + bitmapAndDigestFromDecodeSubset)); + gSuccessfulSubsetDecodes.push_back().printf("\twrote %s", subsetName.c_str()); + + if (!FLAGS_writeChecksumBasedFilenames) { + // FIXME: The goal of extracting the subset is for visual comparison/using skdiff/skpdiff. + // Currently disabling for writeChecksumBasedFilenames since it will be trickier to + // determine which files to compare. + + // Also use extractSubset from the original for visual comparison. + // Write the result to a file in a separate subdirectory. + SkBitmap extractedSubset; + if (!originalBitmap.extractSubset(&extractedSubset, rect)) { + gFailedSubsetDecodes.push_back().printf("Successfully decoded subset %s, but failed " + "to extract a similar subset for comparison.", + subsetName.c_str()); + return false; + } + + SkString dirExtracted = SkOSPath::SkPathJoin(writePath, "extracted"); + if (!sk_mkdir(dirExtracted.c_str())) { + gFailedSubsetDecodes.push_back().printf("Successfully decoded subset%s, but failed " + "to create a directory for extractSubset " + "comparison.", + subsetName.c_str()); + return false; + } + + skiagm::BitmapAndDigest bitmapAndDigestFromExtractSubset(extractedSubset); + SkAssertResult(write_bitmap(dirExtracted.c_str(), subsetName.c_str(), + bitmapAndDigestFromExtractSubset)); + } + return true; +} + +// FIXME: This test could be run on windows/mac once we remove their dependence on +// getLength. See https://code.google.com/p/skia/issues/detail?id=1570 +#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX) + +/** + * Dummy class for testing to ensure that a stream without a length decodes the same + * as a stream with a length. + */ +class FILEStreamWithoutLength : public SkFILEStream { +public: + FILEStreamWithoutLength(const char path[]) + : INHERITED(path) {} + + virtual bool hasLength() const SK_OVERRIDE { + return false; + } + +private: + typedef SkFILEStream INHERITED; +}; + +/** + * Test that decoding a stream which reports to not have a length still results in the + * same image as if it did report to have a length. Assumes that codec was used to + * successfully decode the file using SkFILEStream. + * @param srcPath The path to the file, for recreating the length-less stream. + * @param codec The SkImageDecoder originally used to decode srcPath, which will be used + * again to decode the length-less stream. + * @param digest GmResultDigest computed from decoding the stream the first time. + * Decoding the length-less stream is expected to result in a matching digest. + */ +static void test_stream_without_length(const char srcPath[], SkImageDecoder* codec, + const skiagm::GmResultDigest& digest) { + if (!digest.isValid()) { + // An error was already reported. + return; + } + SkASSERT(srcPath); + SkASSERT(codec); + FILEStreamWithoutLength stream(srcPath); + // This will only be called after a successful decode. Creating a stream from the same + // path should never fail. + SkASSERT(stream.isValid()); + SkBitmap bm; + if (!codec->decode(&stream, &bm, gPrefColorType, SkImageDecoder::kDecodePixels_Mode)) { + gDecodeFailures.push_back().appendf("Without using getLength, %s failed to decode\n", + srcPath); + return; + } + skiagm::GmResultDigest lengthLessDigest(bm); + if (!lengthLessDigest.isValid()) { + gDecodeFailures.push_back().appendf("Without using getLength, %s failed to build " + "a digest\n", srcPath); + return; + } + if (!lengthLessDigest.equals(digest)) { + gDecodeFailures.push_back().appendf("Without using getLength, %s did not match digest " + "that uses getLength\n", srcPath); + } +} +#endif // defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX) + +/** + * Replaces all instances of oldChar with newChar in str. + * + * TODO: This function appears here and in picture_utils.[cpp|h] ; + * we should add the implementation to src/core/SkString.cpp, write tests for it, + * and remove it from elsewhere. + */ +static void replace_char(SkString* str, const char oldChar, const char newChar) { + if (NULL == str) { + return; + } + for (size_t i = 0; i < str->size(); ++i) { + if (oldChar == str->operator[](i)) { + str->operator[](i) = newChar; + } + } +} + +static void decodeFileAndWrite(const char srcPath[], const SkString* writePath) { + SkBitmap bitmap; + SkFILEStream stream(srcPath); + if (!stream.isValid()) { + gInvalidStreams.push_back().set(srcPath); + return; + } + + SkImageDecoder* codec = SkImageDecoder::Factory(&stream); + if (NULL == codec) { + gMissingCodecs.push_back().set(srcPath); + return; + } + + SkAutoTDelete<SkImageDecoder> ad(codec); + + codec->setSkipWritingZeroes(FLAGS_skip); + codec->setSampleSize(FLAGS_sampleSize); + stream.rewind(); + + // Create a string representing just the filename itself, for use in json expectations. + SkString basename = SkOSPath::SkBasename(srcPath); + // Replace '_' with '-', so that the names can fit gm_json.py's IMAGE_FILENAME_PATTERN + replace_char(&basename, '_', '-'); + // Replace '.' with '-', so the output filename can still retain the original file extension, + // but still end up with only one '.', which denotes the actual extension of the final file. + replace_char(&basename, '.', '-'); + const char* filename = basename.c_str(); + + if (!codec->decode(&stream, &bitmap, gPrefColorType, SkImageDecoder::kDecodePixels_Mode)) { + if (NULL != gJsonExpectations.get()) { + const SkString name_config = create_json_key(filename); + skiagm::Expectations jsExpectations = gJsonExpectations->get(name_config.c_str()); + if (jsExpectations.ignoreFailure()) { + // This is a known failure. + gKnownFailures.push_back().appendf( + "failed to decode %s, which is a known failure.", srcPath); + return; + } + if (jsExpectations.empty()) { + // This is a failure, but it is a new file. Mark it as missing, with + // a note that it should be marked failing. + gMissingExpectations.push_back().appendf( + "new file %s (with no expectations) FAILED to decode.", srcPath); + return; + } + } + + // If there was a failure, and either there was no expectations file, or + // the expectations file listed a valid expectation, report the failure. + gDecodeFailures.push_back().set(srcPath); + return; + } + + // Test decoding just the bounds. The bounds should always match. + { + stream.rewind(); + SkBitmap dim; + if (!codec->decode(&stream, &dim, SkImageDecoder::kDecodeBounds_Mode)) { + SkString failure = SkStringPrintf("failed to decode bounds for %s", srcPath); + gDecodeFailures.push_back() = failure; + } else { + // Now check that the bounds match: + if (dim.width() != bitmap.width() || dim.height() != bitmap.height()) { + SkString failure = SkStringPrintf("bounds do not match for %s", srcPath); + gDecodeFailures.push_back() = failure; + } + } + } + + skiagm::BitmapAndDigest bitmapAndDigest(bitmap); + if (compare_to_expectations_if_necessary(bitmapAndDigest.fDigest, filename, &gDecodeFailures, + &gMissingExpectations, &gKnownFailures)) { + gSuccessfulDecodes.push_back().printf("%s [%d %d]", srcPath, bitmap.width(), + bitmap.height()); + } else if (!FLAGS_mismatchPath.isEmpty()) { + if (write_bitmap(FLAGS_mismatchPath[0], filename, bitmapAndDigest)) { + gSuccessfulDecodes.push_back().appendf("\twrote %s", filename); + } else { + gEncodeFailures.push_back().set(filename); + } + } + +// FIXME: This test could be run on windows/mac once we remove their dependence on +// getLength. See https://code.google.com/p/skia/issues/detail?id=1570 +#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX) + test_stream_without_length(srcPath, codec, bitmapAndDigest.fDigest); +#endif + + if (writePath != NULL) { + if (write_bitmap(writePath->c_str(), filename, bitmapAndDigest)) { + gSuccessfulDecodes.push_back().appendf("\twrote %s", filename); + } else { + gEncodeFailures.push_back().set(filename); + } + } + + write_expectations(bitmapAndDigest, filename); + + if (FLAGS_testSubsetDecoding) { + SkDEBUGCODE(bool couldRewind =) stream.rewind(); + SkASSERT(couldRewind); + int width, height; + // Build the tile index for decoding subsets. If the image is 1x1, skip subset + // decoding since there are no smaller subsets. + if (codec->buildTileIndex(&stream, &width, &height) && width > 1 && height > 1) { + SkASSERT(bitmap.width() == width && bitmap.height() == height); + // Call decodeSubset multiple times: + SkRandom rand(0); + for (int i = 0; i < 5; i++) { + SkBitmap bitmapFromDecodeSubset; + // FIXME: Come up with a more representative set of rectangles. + SkIRect rect = generate_random_rect(&rand, width, height); + SkString subsetDim = SkStringPrintf("%d_%d_%d_%d", rect.fLeft, rect.fTop, + rect.fRight, rect.fBottom); + if (codec->decodeSubset(&bitmapFromDecodeSubset, rect, gPrefColorType)) { + SkString subsetName = SkStringPrintf("%s-%s", filename, subsetDim.c_str()); + skiagm::BitmapAndDigest subsetBitmapAndDigest(bitmapFromDecodeSubset); + if (compare_to_expectations_if_necessary(subsetBitmapAndDigest.fDigest, + subsetName.c_str(), + &gFailedSubsetDecodes, + &gMissingSubsetExpectations, + &gKnownSubsetFailures)) { + gSuccessfulSubsetDecodes.push_back().printf("Decoded subset %s from %s", + subsetDim.c_str(), srcPath); + } else if (!FLAGS_mismatchPath.isEmpty()) { + write_subset(FLAGS_mismatchPath[0], subsetName, + subsetBitmapAndDigest, rect, bitmap); + } + + write_expectations(subsetBitmapAndDigest, subsetName.c_str()); + + if (writePath != NULL) { + write_subset(writePath->c_str(), subsetName, + subsetBitmapAndDigest, rect, bitmap); + } + } else { + gFailedSubsetDecodes.push_back().printf("Failed to decode region %s from %s", + subsetDim.c_str(), srcPath); + } + } + } + } + + // Do not attempt to re-encode A8, since our image encoders do not support encoding to A8. + if (FLAGS_reencode && bitmap.colorType() != kAlpha_8_SkColorType) { + // Encode to the format the file was originally in, or PNG if the encoder for the same + // format is unavailable. + SkImageDecoder::Format format = codec->getFormat(); + if (SkImageDecoder::kUnknown_Format == format) { + if (stream.rewind()) { + format = SkImageDecoder::GetStreamFormat(&stream); + } + if (SkImageDecoder::kUnknown_Format == format) { + const char* dot = strrchr(srcPath, '.'); + if (NULL != dot) { + format = guess_format_from_suffix(dot); + } + if (SkImageDecoder::kUnknown_Format == format) { + SkDebugf("Could not determine type for '%s'\n", srcPath); + format = SkImageDecoder::kPNG_Format; + } + + } + } else { + SkASSERT(!stream.rewind() || SkImageDecoder::GetStreamFormat(&stream) == format); + } + SkImageEncoder::Type type = format_to_type(format); + // format should never be kUnknown_Format, so type should never be kUnknown_Type. + SkASSERT(type != SkImageEncoder::kUnknown_Type); + + SkImageEncoder* encoder = SkImageEncoder::Create(type); + if (NULL == encoder) { + type = SkImageEncoder::kPNG_Type; + encoder = SkImageEncoder::Create(type); + SkASSERT(encoder); + } + SkAutoTDelete<SkImageEncoder> ade(encoder); + // Encode to a stream. + SkDynamicMemoryWStream wStream; + if (!encoder->encodeStream(&wStream, bitmap, 100)) { + gEncodeFailures.push_back().printf("Failed to reencode %s to type '%s'", srcPath, + suffix_for_type(type)); + return; + } + + SkAutoTUnref<SkData> data(wStream.copyToData()); + if (writePath != NULL && type != SkImageEncoder::kPNG_Type) { + // Write the encoded data to a file. Do not write to PNG, which was already written. + SkString outPath; + make_outname(&outPath, writePath->c_str(), filename, suffix_for_type(type)); + SkFILEWStream file(outPath.c_str()); + if(file.write(data->data(), data->size())) { + gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str()); + } else { + gEncodeFailures.push_back().printf("Failed to write %s", outPath.c_str()); + } + } + // Ensure that the reencoded data can still be decoded. + SkMemoryStream memStream(data); + SkBitmap redecodedBitmap; + SkImageDecoder::Format formatOnSecondDecode; + if (SkImageDecoder::DecodeStream(&memStream, &redecodedBitmap, gPrefColorType, + SkImageDecoder::kDecodePixels_Mode, + &formatOnSecondDecode)) { + SkASSERT(format_to_type(formatOnSecondDecode) == type); + } else { + gDecodeFailures.push_back().printf("Failed to redecode %s after reencoding to '%s'", + srcPath, suffix_for_type(type)); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +// If strings is not empty, print title, followed by each string on its own line starting +// with a tab. +// @return bool True if strings had at least one entry. +static bool print_strings(const char* title, const SkTArray<SkString, false>& strings) { + if (strings.count() > 0) { + SkDebugf("%s:\n", title); + for (int i = 0; i < strings.count(); i++) { + SkDebugf("\t%s\n", strings[i].c_str()); + } + SkDebugf("\n"); + return true; + } + return false; +} + +/** + * If directory is non null and does not end with a path separator, append one. + * @param directory SkString representing the path to a directory. If the last character is not a + * path separator (specific to the current OS), append one. + */ +static void append_path_separator_if_necessary(SkString* directory) { + if (directory != NULL && directory->c_str()[directory->size() - 1] != SkPATH_SEPARATOR) { + directory->appendf("%c", SkPATH_SEPARATOR); + } +} + +/** + * Return true if the filename represents an image. + */ +static bool is_image_file(const char* filename) { + const char* gImageExtensions[] = { + ".png", ".PNG", ".jpg", ".JPG", ".jpeg", ".JPEG", ".bmp", ".BMP", + ".webp", ".WEBP", ".ico", ".ICO", ".wbmp", ".WBMP", ".gif", ".GIF" + }; + for (size_t i = 0; i < SK_ARRAY_COUNT(gImageExtensions); ++i) { + if (SkStrEndsWith(filename, gImageExtensions[i])) { + return true; + } + } + return false; +} + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + SkCommandLineFlags::SetUsage("Decode files, and optionally write the results to files."); + SkCommandLineFlags::Parse(argc, argv); + + if (FLAGS_readPath.count() < 1) { + SkDebugf("Folder(s) or image(s) to decode are required.\n"); + return -1; + } + + + SkAutoGraphics ag; + + if (!FLAGS_readExpectationsPath.isEmpty() && sk_exists(FLAGS_readExpectationsPath[0])) { + gJsonExpectations.reset(SkNEW_ARGS(skiagm::JsonExpectationsSource, + (FLAGS_readExpectationsPath[0]))); + } + + SkString outDir; + SkString* outDirPtr; + + if (FLAGS_writePath.count() == 1) { + outDir.set(FLAGS_writePath[0]); + append_path_separator_if_necessary(&outDir); + outDirPtr = &outDir; + } else { + outDirPtr = NULL; + } + + if (FLAGS_config.count() == 1) { + // Only consider the first config specified on the command line. + const char* config = FLAGS_config[0]; + if (0 == strcmp(config, "8888")) { + gPrefColorType = kN32_SkColorType; + } else if (0 == strcmp(config, "565")) { + gPrefColorType = kRGB_565_SkColorType; + } else if (0 == strcmp(config, "A8")) { + gPrefColorType = kAlpha_8_SkColorType; + } else if (0 != strcmp(config, "None")) { + SkDebugf("Invalid preferred config\n"); + return -1; + } + } + + for (int i = 0; i < FLAGS_readPath.count(); i++) { + const char* readPath = FLAGS_readPath[i]; + if (strlen(readPath) < 1) { + break; + } + if (sk_isdir(readPath)) { + const char* dir = readPath; + SkOSFile::Iter iter(dir); + SkString filename; + while (iter.next(&filename)) { + if (!is_image_file(filename.c_str())) { + continue; + } + SkString fullname = SkOSPath::SkPathJoin(dir, filename.c_str()); + decodeFileAndWrite(fullname.c_str(), outDirPtr); + } + } else if (sk_exists(readPath) && is_image_file(readPath)) { + decodeFileAndWrite(readPath, outDirPtr); + } + } + + if (!FLAGS_createExpectationsPath.isEmpty()) { + // Use an empty value for everything besides expectations, since the reader only cares + // about the expectations. + Json::Value nullValue; + Json::Value root = skiagm::CreateJsonTree(gExpectationsToWrite, nullValue, nullValue, + nullValue, nullValue); + std::string jsonStdString = root.toStyledString(); + SkFILEWStream stream(FLAGS_createExpectationsPath[0]); + stream.write(jsonStdString.c_str(), jsonStdString.length()); + } + // Add some space, since codecs may print warnings without newline. + SkDebugf("\n\n"); + + bool failed = print_strings("Invalid files", gInvalidStreams); + failed |= print_strings("Missing codec", gMissingCodecs); + failed |= print_strings("Failed to decode", gDecodeFailures); + failed |= print_strings("Failed to encode", gEncodeFailures); + print_strings("Decoded", gSuccessfulDecodes); + print_strings("Missing expectations", gMissingExpectations); + + if (FLAGS_testSubsetDecoding) { + failed |= print_strings("Failed subset decodes", gFailedSubsetDecodes); + print_strings("Decoded subsets", gSuccessfulSubsetDecodes); + print_strings("Missing subset expectations", gMissingSubsetExpectations); + print_strings("Known subset failures", gKnownSubsetFailures); + } + + print_strings("Known failures", gKnownFailures); + + return failed ? -1 : 0; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/skpdiff/README b/chromium/third_party/skia/tools/skpdiff/README new file mode 100644 index 00000000000..b3f8e0b3e02 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/README @@ -0,0 +1,12 @@ +Some example invocations: + +Note how the asterisks are not expanded inside the shell because of the quotes + +out/Debug/skpdiff -p \ + "/usr/local/google/home/zachr/Downloads/gm/*_8888.png" \ + "/usr/local/google/home/zachr/Downloads/gm/*_gpu.png" + + +out/Debug/skpdiff --differs different_pixels -f \ + "/usr/local/google/home/zachr/Downloads/diffs/baseline" \ + "/usr/local/google/home/zachr/Downloads/diffs/test"
\ No newline at end of file diff --git a/chromium/third_party/skia/tools/skpdiff/SkCLImageDiffer.cpp b/chromium/third_party/skia/tools/skpdiff/SkCLImageDiffer.cpp new file mode 100644 index 00000000000..1c5d5c56ea3 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/SkCLImageDiffer.cpp @@ -0,0 +1,122 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <cstring> + +#include "SkBitmap.h" +#include "SkStream.h" + +#include "SkCLImageDiffer.h" +#include "skpdiff_util.h" + +SkCLImageDiffer::SkCLImageDiffer() { + fIsGood = false; +} + +bool SkCLImageDiffer::init(cl_device_id device, cl_context context) { + fContext = context; + fDevice = device; + + cl_int queueErr; + fCommandQueue = clCreateCommandQueue(fContext, fDevice, 0, &queueErr); + if (CL_SUCCESS != queueErr) { + SkDebugf("Command queue creation failed: %s\n", cl_error_to_string(queueErr)); + fIsGood = false; + return false; + } + + fIsGood = this->onInit(); + return fIsGood; +} + +bool SkCLImageDiffer::loadKernelFile(const char file[], const char name[], cl_kernel* kernel) { + // Open the kernel source file + SkFILEStream sourceStream(file); + if (!sourceStream.isValid()) { + SkDebugf("Failed to open kernel source file"); + return false; + } + + return loadKernelStream(&sourceStream, name, kernel); +} + +bool SkCLImageDiffer::loadKernelStream(SkStream* stream, const char name[], cl_kernel* kernel) { + // Read the kernel source into memory + SkString sourceString; + sourceString.resize(stream->getLength()); + size_t bytesRead = stream->read(sourceString.writable_str(), sourceString.size()); + if (bytesRead != sourceString.size()) { + SkDebugf("Failed to read kernel source file"); + return false; + } + + return loadKernelSource(sourceString.c_str(), name, kernel); +} + +bool SkCLImageDiffer::loadKernelSource(const char source[], const char name[], cl_kernel* kernel) { + // Build the kernel source + size_t sourceLen = strlen(source); + cl_program program = clCreateProgramWithSource(fContext, 1, &source, &sourceLen, NULL); + cl_int programErr = clBuildProgram(program, 1, &fDevice, "", NULL, NULL); + if (CL_SUCCESS != programErr) { + SkDebugf("Program creation failed: %s\n", cl_error_to_string(programErr)); + + // Attempt to get information about why the build failed + char buildLog[4096]; + clGetProgramBuildInfo(program, fDevice, CL_PROGRAM_BUILD_LOG, sizeof(buildLog), + buildLog, NULL); + SkDebugf("Build log: %s\n", buildLog); + + return false; + } + + cl_int kernelErr; + *kernel = clCreateKernel(program, name, &kernelErr); + if (CL_SUCCESS != kernelErr) { + SkDebugf("Kernel creation failed: %s\n", cl_error_to_string(kernelErr)); + return false; + } + + return true; +} + +bool SkCLImageDiffer::makeImage2D(SkBitmap* bitmap, cl_mem* image) const { + cl_int imageErr; + cl_image_format bitmapFormat; + switch (bitmap->colorType()) { + case kAlpha_8_SkColorType: + bitmapFormat.image_channel_order = CL_A; + bitmapFormat.image_channel_data_type = CL_UNSIGNED_INT8; + break; + case kRGB_565_SkColorType: + bitmapFormat.image_channel_order = CL_RGB; + bitmapFormat.image_channel_data_type = CL_UNORM_SHORT_565; + break; + case kN32_SkColorType: + bitmapFormat.image_channel_order = CL_RGBA; + bitmapFormat.image_channel_data_type = CL_UNSIGNED_INT8; + break; + default: + SkDebugf("Image format is unsupported\n"); + return false; + } + + // Upload the bitmap data to OpenCL + bitmap->lockPixels(); + *image = clCreateImage2D(fContext, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, + &bitmapFormat, bitmap->width(), bitmap->height(), + bitmap->rowBytes(), bitmap->getPixels(), + &imageErr); + bitmap->unlockPixels(); + + if (CL_SUCCESS != imageErr) { + SkDebugf("Input image creation failed: %s\n", cl_error_to_string(imageErr)); + return false; + } + + return true; +} diff --git a/chromium/third_party/skia/tools/skpdiff/SkCLImageDiffer.h b/chromium/third_party/skia/tools/skpdiff/SkCLImageDiffer.h new file mode 100644 index 00000000000..6e9c2dc0cf6 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/SkCLImageDiffer.h @@ -0,0 +1,97 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCLImageDiffer_DEFINED +#define SkCLImageDiffer_DEFINED + +#if SK_BUILD_FOR_MAC +# include <OpenCL/cl.h> +#else +# include <CL/cl.h> +#endif +#include "SkTDArray.h" + +#include "SkImageDiffer.h" + +class SkStream; + +/** + * An SkImageDiffer that requires initialization with an OpenCL device and context. + */ +class SkCLImageDiffer : public SkImageDiffer { +public: + SkCLImageDiffer(); + + virtual bool requiresOpenCL() const SK_OVERRIDE { return true; } + + /** + * Initializes the OpenCL resources this differ needs to work + * @param device An OpenCL device + * @param context An OpenCL context of the given device + * @return True on success, false otherwise + */ + virtual bool init(cl_device_id device, cl_context context); + +protected: + /** + * Called by init after fDevice, fContext, and fCommandQueue are successfully initialized + * @return True on success, false otherwise + */ + virtual bool onInit() = 0; + + /** + * Loads an OpenCL kernel from the file with the given named entry point. This only works after + * init is called. + * @param file The file path of the kernel + * @param name The name of the entry point of the desired kernel in the file + * @param kernel A pointer to return the loaded kernel into + * @return True on success, false otherwise + */ + bool loadKernelFile(const char file[], const char name[], cl_kernel* kernel); + + /** + * Loads an OpenCL kernel from the stream with the given named entry point. This only works + * after init is called. + * @param stream The stream that contains the kernel + * @param name The name of the entry point of the desired kernel in the stream + * @param kernel A pointer to return the loaded kernel into + * @return True on success, false otherwise + */ + bool loadKernelStream(SkStream* stream, const char name[], cl_kernel* kernel); + + /** + * Loads an OpenCL kernel from the source string with the given named entry point. This only + * works after init is called. + * @param source The string that contains the kernel + * @param name The name of the entry point of the desired kernel in the source string + * @param kernel A pointer to return the loaded kernel into + * @return True on success, false otherwise + */ + bool loadKernelSource(const char source[], const char name[], cl_kernel* kernel); + + /** + * Loads a read only copy of the given bitmap into device memory and returns the block of + * memory. This only works after init is called. + * @param bitmap The bitmap to load into memory + * @param image A pointer to return the allocated image to + * @return True on success, false otherwise + */ + bool makeImage2D(SkBitmap* bitmap, cl_mem* image) const; + + cl_device_id fDevice; + cl_context fContext; + cl_command_queue fCommandQueue; + +protected: + bool fIsGood; + +private: + + typedef SkImageDiffer INHERITED; +}; + +#endif diff --git a/chromium/third_party/skia/tools/skpdiff/SkDiffContext.cpp b/chromium/third_party/skia/tools/skpdiff/SkDiffContext.cpp new file mode 100644 index 00000000000..6f0b09f0822 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/SkDiffContext.cpp @@ -0,0 +1,360 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkBitmap.h" +#include "SkImageDecoder.h" +#include "SkOSFile.h" +#include "SkRunnable.h" +#include "SkStream.h" +#include "SkTDict.h" +#include "SkThreadPool.h" + +#include "SkDiffContext.h" +#include "skpdiff_util.h" + +SkDiffContext::SkDiffContext() { + fDiffers = NULL; + fDifferCount = 0; + fThreadCount = SkThreadPool::kThreadPerCore; +} + +SkDiffContext::~SkDiffContext() { + if (NULL != fDiffers) { + SkDELETE_ARRAY(fDiffers); + } +} + +void SkDiffContext::setDifferenceDir(const SkString& path) { + if (!path.isEmpty() && sk_mkdir(path.c_str())) { + fDifferenceDir = path; + } +} + +void SkDiffContext::setDiffers(const SkTDArray<SkImageDiffer*>& differs) { + // Delete whatever the last array of differs was + if (NULL != fDiffers) { + SkDELETE_ARRAY(fDiffers); + fDiffers = NULL; + fDifferCount = 0; + } + + // Copy over the new differs + fDifferCount = differs.count(); + fDiffers = SkNEW_ARRAY(SkImageDiffer*, fDifferCount); + differs.copy(fDiffers); +} + +static SkString get_common_prefix(const SkString& a, const SkString& b) { + const size_t maxPrefixLength = SkTMin(a.size(), b.size()); + SkASSERT(maxPrefixLength > 0); + for (size_t x = 0; x < maxPrefixLength; ++x) { + if (a[x] != b[x]) { + SkString result; + result.set(a.c_str(), x); + return result; + } + } + if (a.size() > b.size()) { + return b; + } else { + return a; + } +} + +void SkDiffContext::addDiff(const char* baselinePath, const char* testPath) { + // Load the images at the paths + SkBitmap baselineBitmap; + SkBitmap testBitmap; + if (!SkImageDecoder::DecodeFile(baselinePath, &baselineBitmap)) { + SkDebugf("Failed to load bitmap \"%s\"\n", baselinePath); + return; + } + if (!SkImageDecoder::DecodeFile(testPath, &testBitmap)) { + SkDebugf("Failed to load bitmap \"%s\"\n", testPath); + return; + } + + // Setup a record for this diff + fRecordMutex.acquire(); + DiffRecord* newRecord = fRecords.addToHead(DiffRecord()); + fRecordMutex.release(); + + // compute the common name + SkString baseName = SkOSPath::SkBasename(baselinePath); + SkString testName = SkOSPath::SkBasename(testPath); + newRecord->fCommonName = get_common_prefix(baseName, testName); + + newRecord->fBaselinePath = baselinePath; + newRecord->fTestPath = testPath; + + bool alphaMaskPending = false; + + // only enable alpha masks if a difference dir has been provided + if (!fDifferenceDir.isEmpty()) { + alphaMaskPending = true; + } + + // Perform each diff + for (int differIndex = 0; differIndex < fDifferCount; differIndex++) { + SkImageDiffer* differ = fDiffers[differIndex]; + + // Copy the results into data for this record + DiffData& diffData = newRecord->fDiffs.push_back(); + diffData.fDiffName = differ->getName(); + + if (!differ->diff(&baselineBitmap, &testBitmap, alphaMaskPending, &diffData.fResult)) { + // if the diff failed record -1 as the result + diffData.fResult.result = -1; + continue; + } + + if (alphaMaskPending + && SkImageDiffer::RESULT_CORRECT != diffData.fResult.result + && !diffData.fResult.poiAlphaMask.empty() + && !newRecord->fCommonName.isEmpty()) { + + newRecord->fDifferencePath = SkOSPath::SkPathJoin(fDifferenceDir.c_str(), + newRecord->fCommonName.c_str()); + + // compute the image diff and output it + SkBitmap copy; + diffData.fResult.poiAlphaMask.copyTo(©, kN32_SkColorType); + SkImageEncoder::EncodeFile(newRecord->fDifferencePath.c_str(), copy, + SkImageEncoder::kPNG_Type, 100); + + // cleanup the existing bitmap to free up resources; + diffData.fResult.poiAlphaMask.reset(); + + alphaMaskPending = false; + } + } +} + +class SkThreadedDiff : public SkRunnable { +public: + SkThreadedDiff() : fDiffContext(NULL) { } + + void setup(SkDiffContext* diffContext, const SkString& baselinePath, const SkString& testPath) { + fDiffContext = diffContext; + fBaselinePath = baselinePath; + fTestPath = testPath; + } + + virtual void run() SK_OVERRIDE { + fDiffContext->addDiff(fBaselinePath.c_str(), fTestPath.c_str()); + } + +private: + SkDiffContext* fDiffContext; + SkString fBaselinePath; + SkString fTestPath; +}; + +void SkDiffContext::diffDirectories(const char baselinePath[], const char testPath[]) { + // Get the files in the baseline, we will then look for those inside the test path + SkTArray<SkString> baselineEntries; + if (!get_directory(baselinePath, &baselineEntries)) { + SkDebugf("Unable to open path \"%s\"\n", baselinePath); + return; + } + + SkThreadPool threadPool(fThreadCount); + SkTArray<SkThreadedDiff> runnableDiffs; + runnableDiffs.reset(baselineEntries.count()); + + for (int x = 0; x < baselineEntries.count(); x++) { + const char* baseFilename = baselineEntries[x].c_str(); + + // Find the real location of each file to compare + SkString baselineFile = SkOSPath::SkPathJoin(baselinePath, baseFilename); + SkString testFile = SkOSPath::SkPathJoin(testPath, baseFilename); + + // Check that the test file exists and is a file + if (sk_exists(testFile.c_str()) && !sk_isdir(testFile.c_str())) { + // Queue up the comparison with the differ + runnableDiffs[x].setup(this, baselineFile, testFile); + threadPool.add(&runnableDiffs[x]); + } else { + SkDebugf("Baseline file \"%s\" has no corresponding test file\n", baselineFile.c_str()); + } + } + + threadPool.wait(); +} + + +void SkDiffContext::diffPatterns(const char baselinePattern[], const char testPattern[]) { + // Get the files in the baseline and test patterns. Because they are in sorted order, it's easy + // to find corresponding images by matching entry indices. + + SkTArray<SkString> baselineEntries; + if (!glob_files(baselinePattern, &baselineEntries)) { + SkDebugf("Unable to get pattern \"%s\"\n", baselinePattern); + return; + } + + SkTArray<SkString> testEntries; + if (!glob_files(testPattern, &testEntries)) { + SkDebugf("Unable to get pattern \"%s\"\n", testPattern); + return; + } + + if (baselineEntries.count() != testEntries.count()) { + SkDebugf("Baseline and test patterns do not yield corresponding number of files\n"); + return; + } + + SkThreadPool threadPool(fThreadCount); + SkTArray<SkThreadedDiff> runnableDiffs; + runnableDiffs.reset(baselineEntries.count()); + + for (int x = 0; x < baselineEntries.count(); x++) { + runnableDiffs[x].setup(this, baselineEntries[x], testEntries[x]); + threadPool.add(&runnableDiffs[x]); + } + + threadPool.wait(); +} + +void SkDiffContext::outputRecords(SkWStream& stream, bool useJSONP) { + SkTLList<DiffRecord>::Iter iter(fRecords, SkTLList<DiffRecord>::Iter::kHead_IterStart); + DiffRecord* currentRecord = iter.get(); + + if (useJSONP) { + stream.writeText("var SkPDiffRecords = {\n"); + } else { + stream.writeText("{\n"); + } + stream.writeText(" \"records\": [\n"); + while (NULL != currentRecord) { + stream.writeText(" {\n"); + + SkString differenceAbsPath = get_absolute_path(currentRecord->fDifferencePath); + SkString baselineAbsPath = get_absolute_path(currentRecord->fBaselinePath); + SkString testAbsPath = get_absolute_path(currentRecord->fTestPath); + + stream.writeText(" \"commonName\": \""); + stream.writeText(currentRecord->fCommonName.c_str()); + stream.writeText("\",\n"); + + stream.writeText(" \"differencePath\": \""); + stream.writeText(differenceAbsPath.c_str()); + stream.writeText("\",\n"); + + stream.writeText(" \"baselinePath\": \""); + stream.writeText(baselineAbsPath.c_str()); + stream.writeText("\",\n"); + + stream.writeText(" \"testPath\": \""); + stream.writeText(testAbsPath.c_str()); + stream.writeText("\",\n"); + + stream.writeText(" \"diffs\": [\n"); + for (int diffIndex = 0; diffIndex < currentRecord->fDiffs.count(); diffIndex++) { + DiffData& data = currentRecord->fDiffs[diffIndex]; + stream.writeText(" {\n"); + + stream.writeText(" \"differName\": \""); + stream.writeText(data.fDiffName); + stream.writeText("\",\n"); + + stream.writeText(" \"result\": "); + stream.writeScalarAsText((SkScalar)data.fResult.result); + stream.writeText(",\n"); + + stream.writeText(" \"pointsOfInterest\": "); + stream.writeDecAsText(data.fResult.poiCount); + stream.writeText("\n"); + + stream.writeText(" }"); + + // JSON does not allow trailing commas + if (diffIndex + 1 < currentRecord->fDiffs.count()) { + stream.writeText(","); + } + stream.writeText(" \n"); + } + stream.writeText(" ]\n"); + + stream.writeText(" }"); + + currentRecord = iter.next(); + + // JSON does not allow trailing commas + if (NULL != currentRecord) { + stream.writeText(","); + } + stream.writeText("\n"); + } + stream.writeText(" ]\n"); + if (useJSONP) { + stream.writeText("};\n"); + } else { + stream.writeText("}\n"); + } +} + +void SkDiffContext::outputCsv(SkWStream& stream) { + SkTDict<int> columns(2); + int cntColumns = 0; + + stream.writeText("key"); + + SkTLList<DiffRecord>::Iter iter(fRecords, SkTLList<DiffRecord>::Iter::kHead_IterStart); + DiffRecord* currentRecord = iter.get(); + + // Write CSV header and create a dictionary of all columns. + while (NULL != currentRecord) { + for (int diffIndex = 0; diffIndex < currentRecord->fDiffs.count(); diffIndex++) { + DiffData& data = currentRecord->fDiffs[diffIndex]; + if (!columns.find(data.fDiffName)) { + columns.set(data.fDiffName, cntColumns); + stream.writeText(", "); + stream.writeText(data.fDiffName); + cntColumns++; + } + } + currentRecord = iter.next(); + } + stream.writeText("\n"); + + double values[100]; + SkASSERT(cntColumns < 100); // Make the array larger, if we ever have so many diff types. + + SkTLList<DiffRecord>::Iter iter2(fRecords, SkTLList<DiffRecord>::Iter::kHead_IterStart); + currentRecord = iter2.get(); + while (NULL != currentRecord) { + for (int i = 0; i < cntColumns; i++) { + values[i] = -1; + } + + for (int diffIndex = 0; diffIndex < currentRecord->fDiffs.count(); diffIndex++) { + DiffData& data = currentRecord->fDiffs[diffIndex]; + int index = -1; + SkAssertResult(columns.find(data.fDiffName, &index)); + SkASSERT(index >= 0 && index < cntColumns); + values[index] = data.fResult.result; + } + + const char* filename = currentRecord->fBaselinePath.c_str() + + strlen(currentRecord->fBaselinePath.c_str()) - 1; + while (filename > currentRecord->fBaselinePath.c_str() && *(filename - 1) != '/') { + filename--; + } + + stream.writeText(filename); + + for (int i = 0; i < cntColumns; i++) { + SkString str; + str.printf(", %f", values[i]); + stream.writeText(str.c_str()); + } + stream.writeText("\n"); + + currentRecord = iter2.next(); + } +} diff --git a/chromium/third_party/skia/tools/skpdiff/SkDiffContext.h b/chromium/third_party/skia/tools/skpdiff/SkDiffContext.h new file mode 100644 index 00000000000..c036c2ef6cd --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/SkDiffContext.h @@ -0,0 +1,143 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDiffContext_DEFINED +#define SkDiffContext_DEFINED + +#include "SkImageDiffer.h" +#include "SkString.h" +#include "SkTArray.h" +#include "SkTDArray.h" +#include "SkTLList.h" +#include "SkThread.h" + +class SkWStream; + +/** + * Collects records of diffs and outputs them as JSON. + */ +class SkDiffContext { +public: + SkDiffContext(); + ~SkDiffContext(); + + void setThreadCount(int threadCount) { fThreadCount = threadCount; } + + /** + * Creates the directory if it does not exist and uses it to store differences + * between images. + */ + void setDifferenceDir(const SkString& directory); + + /** + * Sets the differs to be used in each diff. Already started diffs will not retroactively use + * these. + * @param differs An array of differs to use. The array is copied, but not the differs + * themselves. + */ + void setDiffers(const SkTDArray<SkImageDiffer*>& differs); + + /** + * Compares two directories of images with the given differ + * @param baselinePath The baseline directory's path + * @param testPath The test directory's path + */ + void diffDirectories(const char baselinePath[], const char testPath[]); + + /** + * Compares two sets of images identified by glob style patterns with the given differ + * @param baselinePattern A pattern for baseline files + * @param testPattern A pattern for test files that matches each file of the baseline file + */ + void diffPatterns(const char baselinePattern[], const char testPattern[]); + + /** + * Compares the images at the given paths + * @param baselinePath The baseline file path + * @param testPath The matching test file path + */ + void addDiff(const char* baselinePath, const char* testPath); + + /** + * Output the records of each diff in JSON. + * + * The format of the JSON document is one top level array named "records". + * Each record in the array is an object with the following values: + * "commonName" : string containing the common prefix of the baselinePath + * and testPath filenames + * "baselinePath" : string containing the path to the baseline image + * "testPath" : string containing the path to the test image + * "differencePath" : (optional) string containing the path to an alpha + * mask of the pixel difference between the baseline + * and test images + * + * They also have an array named "diffs" with each element being one diff record for the two + * images indicated in the above field. + * A diff record includes: + * "differName" : string name of the diff metric used + * "result" : numerical result of the diff + * + * Here is an example: + * + * { + * "records": [ + * { + * "commonName": "queue.png", + * "baselinePath": "/a/queue.png", + * "testPath": "/b/queue.png", + * "diffs": [ + * { + * "differName": "different_pixels", + * "result": 1, + * } + * ] + * } + * ] + * } + * + * @param stream The stream to output the diff to + * @param useJSONP True to adding padding to the JSON output to make it cross-site requestable. + */ + void outputRecords(SkWStream& stream, bool useJSONP); + + /** + * Output the records score in csv format. + */ + void outputCsv(SkWStream& stream); + + +private: + struct DiffData { + const char* fDiffName; + SkImageDiffer::Result fResult; + }; + + struct DiffRecord { + SkString fCommonName; + SkString fDifferencePath; + SkString fBaselinePath; + SkString fTestPath; + SkTArray<DiffData> fDiffs; + }; + + // Used to protect access to fRecords and ensure only one thread is + // adding new entries at a time. + SkMutex fRecordMutex; + + // We use linked list for the records so that their pointers remain stable. A resizable array + // might change its pointers, which would make it harder for async diffs to record their + // results. + SkTLList<DiffRecord> fRecords; + + SkImageDiffer** fDiffers; + int fDifferCount; + int fThreadCount; + + SkString fDifferenceDir; +}; + +#endif diff --git a/chromium/third_party/skia/tools/skpdiff/SkDifferentPixelsMetric.h b/chromium/third_party/skia/tools/skpdiff/SkDifferentPixelsMetric.h new file mode 100644 index 00000000000..06c56b1cca2 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/SkDifferentPixelsMetric.h @@ -0,0 +1,49 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDifferentPixelsMetric_DEFINED +#define SkDifferentPixelsMetric_DEFINED + +#include "SkTDArray.h" + +#if SK_SUPPORT_OPENCL +#include "SkCLImageDiffer.h" +#else +#include "SkImageDiffer.h" +#endif + +/** + * A differ that measures the percentage of different corresponding pixels. If the two images are + * not the same size or have no pixels, the result will always be zero. + */ +class SkDifferentPixelsMetric : +#if SK_SUPPORT_OPENCL + public SkCLImageDiffer { +#else + public SkImageDiffer { +#endif +public: + virtual const char* getName() const SK_OVERRIDE; + virtual bool diff(SkBitmap* baseline, SkBitmap* test, bool computeMask, + Result* result) const SK_OVERRIDE; + +protected: +#if SK_SUPPORT_OPENCL + virtual bool onInit() SK_OVERRIDE; +#endif + +private: +#if SK_SUPPORT_OPENCL + cl_kernel fKernel; + + typedef SkCLImageDiffer INHERITED; +#else + typedef SkImageDiffer INHERITED; +#endif +}; + +#endif diff --git a/chromium/third_party/skia/tools/skpdiff/SkDifferentPixelsMetric_cpu.cpp b/chromium/third_party/skia/tools/skpdiff/SkDifferentPixelsMetric_cpu.cpp new file mode 100644 index 00000000000..e528b78f4fa --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/SkDifferentPixelsMetric_cpu.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkDifferentPixelsMetric.h" + +#include "SkBitmap.h" +#include "skpdiff_util.h" + +const char* SkDifferentPixelsMetric::getName() const { + return "different_pixels"; +} + +bool SkDifferentPixelsMetric::diff(SkBitmap* baseline, SkBitmap* test, bool computeMask, + Result* result) const { + double startTime = get_seconds(); + + // Ensure the images are comparable + if (baseline->width() != test->width() || baseline->height() != test->height() || + baseline->width() <= 0 || baseline->height() <= 0 || + baseline->colorType() != test->colorType()) { + return false; + } + + int width = baseline->width(); + int height = baseline->height(); + + // Prepare the POI alpha mask if needed + if (computeMask) { + result->poiAlphaMask.allocPixels(SkImageInfo::MakeA8(width, height)); + result->poiAlphaMask.eraseARGB(SK_AlphaOPAQUE, 0, 0, 0); + } + + // Prepare the pixels for comparison + result->poiCount = 0; + baseline->lockPixels(); + test->lockPixels(); + for (int y = 0; y < height; y++) { + // Grab a row from each image for easy comparison + unsigned char* baselineRow = (unsigned char*)baseline->getAddr(0, y); + unsigned char* testRow = (unsigned char*)test->getAddr(0, y); + for (int x = 0; x < width; x++) { + // Compare one pixel at a time so each differing pixel can be noted + if (memcmp(&baselineRow[x * 4], &testRow[x * 4], 4) != 0) { + result->poiCount++; + if (computeMask) { + *result->poiAlphaMask.getAddr8(x,y) = SK_AlphaTRANSPARENT; + } + } + } + } + test->unlockPixels(); + baseline->unlockPixels(); + + if (computeMask) { + result->poiAlphaMask.unlockPixels(); + } + + // Calculates the percentage of identical pixels + result->result = 1.0 - ((double)result->poiCount / (width * height)); + result->timeElapsed = get_seconds() - startTime; + + return true; +} diff --git a/chromium/third_party/skia/tools/skpdiff/SkDifferentPixelsMetric_opencl.cpp b/chromium/third_party/skia/tools/skpdiff/SkDifferentPixelsMetric_opencl.cpp new file mode 100644 index 00000000000..14225055f15 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/SkDifferentPixelsMetric_opencl.cpp @@ -0,0 +1,120 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkBitmap.h" + +#include "SkDifferentPixelsMetric.h" +#include "skpdiff_util.h" + +static const char kDifferentPixelsKernelSource[] = + "#pragma OPENCL_EXTENSION cl_khr_global_int32_base_atomics \n" + " \n" + "const sampler_t gInSampler = CLK_NORMALIZED_COORDS_FALSE | \n" + " CLK_ADDRESS_CLAMP_TO_EDGE | \n" + " CLK_FILTER_NEAREST; \n" + " \n" + "__kernel void diff(read_only image2d_t baseline, read_only image2d_t test, \n" + " __global int* result) { \n" + " int2 coord = (int2)(get_global_id(0), get_global_id(1)); \n" + " uint4 baselinePixel = read_imageui(baseline, gInSampler, coord); \n" + " uint4 testPixel = read_imageui(test, gInSampler, coord); \n" + " if (baselinePixel.x != testPixel.x || \n" + " baselinePixel.y != testPixel.y || \n" + " baselinePixel.z != testPixel.z || \n" + " baselinePixel.w != testPixel.w) { \n" + " \n" + " atomic_inc(result); \n" + " // TODO: generate alpha mask \n" + " } \n" + "} \n"; + +const char* SkDifferentPixelsMetric::getName() const { + return "different_pixels"; +} + +bool SkDifferentPixelsMetric::diff(SkBitmap* baseline, SkBitmap* test, bool computeMask, + Result* result) const { + double startTime = get_seconds(); + + if (!fIsGood) { + return false; + } + + // If we never end up running the kernel, include some safe defaults in the result. + result->poiCount = 0; + + // Ensure the images are comparable + if (baseline->width() != test->width() || baseline->height() != test->height() || + baseline->width() <= 0 || baseline->height() <= 0 || + baseline->config() != test->config()) { + return false; + } + + cl_mem baselineImage; + cl_mem testImage; + cl_mem resultsBuffer; + + // Upload images to the CL device + if (!this->makeImage2D(baseline, &baselineImage) || !this->makeImage2D(test, &testImage)) { + SkDebugf("creation of openCL images failed"); + return false; + } + + // A small hack that makes calculating percentage difference easier later on. + result->result = 1.0 / ((double)baseline->width() * baseline->height()); + + // Make a buffer to store results into. It must be initialized with pointers to memory. + static const int kZero = 0; + // We know OpenCL won't write to it because we use CL_MEM_COPY_HOST_PTR + resultsBuffer = clCreateBuffer(fContext, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, + sizeof(int), (int*)&kZero, NULL); + + // Set all kernel arguments + cl_int setArgErr = clSetKernelArg(fKernel, 0, sizeof(cl_mem), &baselineImage); + setArgErr |= clSetKernelArg(fKernel, 1, sizeof(cl_mem), &testImage); + setArgErr |= clSetKernelArg(fKernel, 2, sizeof(cl_mem), &resultsBuffer); + if (CL_SUCCESS != setArgErr) { + SkDebugf("Set arg failed: %s\n", cl_error_to_string(setArgErr)); + return false; + } + + // Queue this diff on the CL device + cl_event event; + const size_t workSize[] = { baseline->width(), baseline->height() }; + cl_int enqueueErr; + enqueueErr = clEnqueueNDRangeKernel(fCommandQueue, fKernel, 2, NULL, workSize, + NULL, 0, NULL, &event); + if (CL_SUCCESS != enqueueErr) { + SkDebugf("Enqueue failed: %s\n", cl_error_to_string(enqueueErr)); + return false; + } + + // This makes things totally synchronous. Actual queue is not ready yet + clWaitForEvents(1, &event); + + // Immediate read back the results + clEnqueueReadBuffer(fCommandQueue, resultsBuffer, CL_TRUE, 0, + sizeof(int), &result->poiCount, 0, NULL, NULL); + result->result *= (double)result->poiCount; + result->result = (1.0 - result->result); + + // Release all the buffers created + clReleaseMemObject(resultsBuffer); + clReleaseMemObject(baselineImage); + clReleaseMemObject(testImage); + + result->timeElapsed = get_seconds() - startTime; + return true; +} + +bool SkDifferentPixelsMetric::onInit() { + if (!this->loadKernelSource(kDifferentPixelsKernelSource, "diff", &fKernel)) { + return false; + } + + return true; +} diff --git a/chromium/third_party/skia/tools/skpdiff/SkImageDiffer.cpp b/chromium/third_party/skia/tools/skpdiff/SkImageDiffer.cpp new file mode 100644 index 00000000000..2ceba6d80e6 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/SkImageDiffer.cpp @@ -0,0 +1,19 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkImageDiffer.h" + +const double SkImageDiffer::RESULT_CORRECT = 1.0f; +const double SkImageDiffer::RESULT_INCORRECT = 0.0f; + +SkImageDiffer::SkImageDiffer() { + +} + +SkImageDiffer::~SkImageDiffer() { + +} diff --git a/chromium/third_party/skia/tools/skpdiff/SkImageDiffer.h b/chromium/third_party/skia/tools/skpdiff/SkImageDiffer.h new file mode 100644 index 00000000000..641bbe8f8f8 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/SkImageDiffer.h @@ -0,0 +1,53 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageDiffer_DEFINED +#define SkImageDiffer_DEFINED + +#include "SkBitmap.h" + +/** + * Encapsulates an image difference metric algorithm that can be potentially run asynchronously. + */ +class SkImageDiffer { +public: + SkImageDiffer(); + virtual ~SkImageDiffer(); + + static const double RESULT_CORRECT; + static const double RESULT_INCORRECT; + + struct Result { + double result; + int poiCount; + SkBitmap poiAlphaMask; // optional + double timeElapsed; // optional + }; + + /** + * Gets a unique and descriptive name of this differ + * @return A statically allocated null terminated string that is the name of this differ + */ + virtual const char* getName() const = 0; + + /** + * Gets if this differ needs to be initialized with and OpenCL device and context. + */ + virtual bool requiresOpenCL() const { return false; } + + /** + * diff on a pair of bitmaps. + * @param baseline The correct bitmap + * @param test The bitmap whose difference is being tested + * @param computeMask true if the differ is to attempt to create poiAlphaMask + * @return true on success, and false in the case of failure + */ + virtual bool diff(SkBitmap* baseline, SkBitmap* test, bool computeMask, + Result* result) const = 0; +}; + +#endif diff --git a/chromium/third_party/skia/tools/skpdiff/SkPMetric.cpp b/chromium/third_party/skia/tools/skpdiff/SkPMetric.cpp new file mode 100644 index 00000000000..a433d9f4b30 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/SkPMetric.cpp @@ -0,0 +1,466 @@ +#include <cmath> +#include <math.h> + +#include "SkBitmap.h" +#include "skpdiff_util.h" +#include "SkPMetric.h" +#include "SkPMetricUtil_generated.h" + +struct RGB { + float r, g, b; +}; + +struct LAB { + float l, a, b; +}; + +template<class T> +struct Image2D { + int width; + int height; + T* image; + + Image2D(int w, int h) + : width(w), + height(h) { + SkASSERT(w > 0); + SkASSERT(h > 0); + image = SkNEW_ARRAY(T, w * h); + } + + ~Image2D() { + SkDELETE_ARRAY(image); + } + + void readPixel(int x, int y, T* pixel) const { + SkASSERT(x >= 0); + SkASSERT(y >= 0); + SkASSERT(x < width); + SkASSERT(y < height); + *pixel = image[y * width + x]; + } + + T* getRow(int y) const { + return &image[y * width]; + } + + void writePixel(int x, int y, const T& pixel) { + SkASSERT(x >= 0); + SkASSERT(y >= 0); + SkASSERT(x < width); + SkASSERT(y < height); + image[y * width + x] = pixel; + } +}; + +typedef Image2D<float> ImageL; +typedef Image2D<RGB> ImageRGB; +typedef Image2D<LAB> ImageLAB; + +template<class T> +struct ImageArray +{ + int slices; + Image2D<T>** image; + + ImageArray(int w, int h, int s) + : slices(s) { + SkASSERT(s > 0); + image = SkNEW_ARRAY(Image2D<T>*, s); + for (int sliceIndex = 0; sliceIndex < slices; sliceIndex++) { + image[sliceIndex] = SkNEW_ARGS(Image2D<T>, (w, h)); + } + } + + ~ImageArray() { + for (int sliceIndex = 0; sliceIndex < slices; sliceIndex++) { + SkDELETE(image[sliceIndex]); + } + SkDELETE_ARRAY(image); + } + + Image2D<T>* getLayer(int z) const { + SkASSERT(z >= 0); + SkASSERT(z < slices); + return image[z]; + } +}; + +typedef ImageArray<float> ImageL3D; + + +#define MAT_ROW_MULT(rc,gc,bc) r*rc + g*gc + b*bc + +static void adobergb_to_cielab(float r, float g, float b, LAB* lab) { + // Conversion of Adobe RGB to XYZ taken from from "Adobe RGB (1998) ColorImage Encoding" + // URL:http://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf + // Section: 4.3.5.3 + // See Also: http://en.wikipedia.org/wiki/Adobe_rgb + float x = MAT_ROW_MULT(0.57667f, 0.18556f, 0.18823f); + float y = MAT_ROW_MULT(0.29734f, 0.62736f, 0.07529f); + float z = MAT_ROW_MULT(0.02703f, 0.07069f, 0.99134f); + + // The following is the white point in XYZ, so it's simply the row wise addition of the above + // matrix. + const float xw = 0.5767f + 0.185556f + 0.188212f; + const float yw = 0.297361f + 0.627355f + 0.0752847f; + const float zw = 0.0270328f + 0.0706879f + 0.991248f; + + // This is the XYZ color point relative to the white point + float f[3] = { x / xw, y / yw, z / zw }; + + // Conversion from XYZ to LAB taken from + // http://en.wikipedia.org/wiki/CIELAB#Forward_transformation + for (int i = 0; i < 3; i++) { + if (f[i] >= 0.008856f) { + f[i] = SkPMetricUtil::get_cube_root(f[i]); + } else { + f[i] = 7.787f * f[i] + 4.0f / 29.0f; + } + } + lab->l = 116.0f * f[1] - 16.0f; + lab->a = 500.0f * (f[0] - f[1]); + lab->b = 200.0f * (f[1] - f[2]); +} + +/// Converts a 8888 bitmap to LAB color space and puts it into the output +static bool bitmap_to_cielab(const SkBitmap* bitmap, ImageLAB* outImageLAB) { + SkBitmap bm8888; + if (bitmap->colorType() != kN32_SkColorType) { + if (!bitmap->copyTo(&bm8888, kN32_SkColorType)) { + return false; + } + bitmap = &bm8888; + } + + int width = bitmap->width(); + int height = bitmap->height(); + SkASSERT(outImageLAB->width == width); + SkASSERT(outImageLAB->height == height); + + bitmap->lockPixels(); + RGB rgb; + LAB lab; + for (int y = 0; y < height; y++) { + unsigned char* row = (unsigned char*)bitmap->getAddr(0, y); + for (int x = 0; x < width; x++) { + // Perform gamma correction which is assumed to be 2.2 + rgb.r = SkPMetricUtil::get_gamma(row[x * 4 + 2]); + rgb.g = SkPMetricUtil::get_gamma(row[x * 4 + 1]); + rgb.b = SkPMetricUtil::get_gamma(row[x * 4 + 0]); + adobergb_to_cielab(rgb.r, rgb.g, rgb.b, &lab); + outImageLAB->writePixel(x, y, lab); + } + } + bitmap->unlockPixels(); + return true; +} + +// From Barten SPIE 1989 +static float contrast_sensitivity(float cyclesPerDegree, float luminance) { + float a = 440.0f * powf(1.0f + 0.7f / luminance, -0.2f); + float b = 0.3f * powf(1.0f + 100.0f / luminance, 0.15f); + float exp = expf(-b * cyclesPerDegree); + float root = sqrtf(1.0f + 0.06f * expf(b * cyclesPerDegree)); + if (!SkScalarIsFinite(exp) || !SkScalarIsFinite(root)) { + return 0; + } + return a * cyclesPerDegree * exp * root; +} + +#if 0 +// We're keeping these around for reference and in case the lookup tables are no longer desired. +// They are no longer called by any code in this file. + +// From Daly 1993 + static float visual_mask(float contrast) { + float x = powf(392.498f * contrast, 0.7f); + x = powf(0.0153f * x, 4.0f); + return powf(1.0f + x, 0.25f); +} + +// From Ward Larson Siggraph 1997 +static float threshold_vs_intensity(float adaptationLuminance) { + float logLum = log10f(adaptationLuminance); + float x; + if (logLum < -3.94f) { + x = -2.86f; + } else if (logLum < -1.44f) { + x = powf(0.405f * logLum + 1.6f, 2.18) - 2.86f; + } else if (logLum < -0.0184f) { + x = logLum - 0.395f; + } else if (logLum < 1.9f) { + x = powf(0.249f * logLum + 0.65f, 2.7f) - 0.72f; + } else { + x = logLum - 1.255f; + } + return powf(10.0f, x); +} + +#endif + +/// Simply takes the L channel from the input and puts it into the output +static void lab_to_l(const ImageLAB* imageLAB, ImageL* outImageL) { + for (int y = 0; y < imageLAB->height; y++) { + for (int x = 0; x < imageLAB->width; x++) { + LAB lab; + imageLAB->readPixel(x, y, &lab); + outImageL->writePixel(x, y, lab.l); + } + } +} + +/// Convolves an image with the given filter in one direction and saves it to the output image +static void convolve(const ImageL* imageL, bool vertical, ImageL* outImageL) { + SkASSERT(imageL->width == outImageL->width); + SkASSERT(imageL->height == outImageL->height); + + const float matrix[] = { 0.05f, 0.25f, 0.4f, 0.25f, 0.05f }; + const int matrixCount = sizeof(matrix) / sizeof(float); + const int radius = matrixCount / 2; + + // Keep track of what rows are being operated on for quick access. + float* rowPtrs[matrixCount]; // Because matrixCount is constant, this won't create a VLA + for (int y = radius; y < matrixCount; y++) { + rowPtrs[y] = imageL->getRow(y - radius); + } + float* writeRow = outImageL->getRow(0); + + for (int y = 0; y < imageL->height; y++) { + for (int x = 0; x < imageL->width; x++) { + float lSum = 0.0f; + for (int xx = -radius; xx <= radius; xx++) { + int nx = x; + int ny = y; + + // We mirror at edges so that edge pixels that the filter weighting still makes + // sense. + if (vertical) { + ny += xx; + if (ny < 0) { + ny = -ny; + } + if (ny >= imageL->height) { + ny = imageL->height + (imageL->height - ny - 1); + } + } else { + nx += xx; + if (nx < 0) { + nx = -nx; + } + if (nx >= imageL->width) { + nx = imageL->width + (imageL->width - nx - 1); + } + } + + float weight = matrix[xx + radius]; + lSum += rowPtrs[ny - y + radius][nx] * weight; + } + writeRow[x] = lSum; + } + // As we move down, scroll the row pointers down with us + for (int y = 0; y < matrixCount - 1; y++) + { + rowPtrs[y] = rowPtrs[y + 1]; + } + rowPtrs[matrixCount - 1] += imageL->width; + writeRow += imageL->width; + } +} + +static double pmetric(const ImageLAB* baselineLAB, const ImageLAB* testLAB, int* poiCount) { + SkASSERT(baselineLAB); + SkASSERT(testLAB); + SkASSERT(poiCount); + + int width = baselineLAB->width; + int height = baselineLAB->height; + int maxLevels = 0; + + // Calculates how many levels to make by how many times the image can be divided in two + int smallerDimension = width < height ? width : height; + for ( ; smallerDimension > 1; smallerDimension /= 2) { + maxLevels++; + } + + // We'll be creating new arrays with maxLevels - 2, and ImageL3D requires maxLevels > 0, + // so just return failure if we're less than 3. + if (maxLevels <= 2) { + return 0.0; + } + + const float fov = SK_ScalarPI / 180.0f * 45.0f; + float contrastSensitivityMax = contrast_sensitivity(3.248f, 100.0f); + float pixelsPerDegree = width / (2.0f * tanf(fov * 0.5f) * 180.0f / SK_ScalarPI); + + ImageL3D baselineL(width, height, maxLevels); + ImageL3D testL(width, height, maxLevels); + ImageL scratchImageL(width, height); + float* cyclesPerDegree = SkNEW_ARRAY(float, maxLevels); + float* thresholdFactorFrequency = SkNEW_ARRAY(float, maxLevels - 2); + float* contrast = SkNEW_ARRAY(float, maxLevels - 2); + + lab_to_l(baselineLAB, baselineL.getLayer(0)); + lab_to_l(testLAB, testL.getLayer(0)); + + // Compute cpd - Cycles per degree on the pyramid + cyclesPerDegree[0] = 0.5f * pixelsPerDegree; + for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { + cyclesPerDegree[levelIndex] = cyclesPerDegree[levelIndex - 1] * 0.5f; + } + + // Contrast sensitivity is based on image dimensions. Therefore it cannot be statically + // generated. + float* contrastSensitivityTable = SkNEW_ARRAY(float, maxLevels * 1000); + for (int levelIndex = 0; levelIndex < maxLevels; levelIndex++) { + for (int csLum = 0; csLum < 1000; csLum++) { + contrastSensitivityTable[levelIndex * 1000 + csLum] = + contrast_sensitivity(cyclesPerDegree[levelIndex], (float)csLum / 10.0f + 1e-5f); + } + } + + // Compute G - The convolved lum for the baseline + for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { + convolve(baselineL.getLayer(levelIndex - 1), false, &scratchImageL); + convolve(&scratchImageL, true, baselineL.getLayer(levelIndex)); + } + for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { + convolve(testL.getLayer(levelIndex - 1), false, &scratchImageL); + convolve(&scratchImageL, true, testL.getLayer(levelIndex)); + } + + // Compute F_freq - The elevation f + for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) { + float cpd = cyclesPerDegree[levelIndex]; + thresholdFactorFrequency[levelIndex] = contrastSensitivityMax / + contrast_sensitivity(cpd, 100.0f); + } + + // Calculate F + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + float lBaseline; + float lTest; + baselineL.getLayer(0)->readPixel(x, y, &lBaseline); + testL.getLayer(0)->readPixel(x, y, &lTest); + + float avgLBaseline; + float avgLTest; + baselineL.getLayer(maxLevels - 1)->readPixel(x, y, &avgLBaseline); + testL.getLayer(maxLevels - 1)->readPixel(x, y, &avgLTest); + + float lAdapt = 0.5f * (avgLBaseline + avgLTest); + if (lAdapt < 1e-5f) { + lAdapt = 1e-5f; + } + + float contrastSum = 0.0f; + for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) { + float baselineL0, baselineL1, baselineL2; + float testL0, testL1, testL2; + baselineL.getLayer(levelIndex + 0)->readPixel(x, y, &baselineL0); + testL. getLayer(levelIndex + 0)->readPixel(x, y, &testL0); + baselineL.getLayer(levelIndex + 1)->readPixel(x, y, &baselineL1); + testL. getLayer(levelIndex + 1)->readPixel(x, y, &testL1); + baselineL.getLayer(levelIndex + 2)->readPixel(x, y, &baselineL2); + testL. getLayer(levelIndex + 2)->readPixel(x, y, &testL2); + + float baselineContrast1 = fabsf(baselineL0 - baselineL1); + float testContrast1 = fabsf(testL0 - testL1); + float numerator = (baselineContrast1 > testContrast1) ? + baselineContrast1 : testContrast1; + + float baselineContrast2 = fabsf(baselineL2); + float testContrast2 = fabsf(testL2); + float denominator = (baselineContrast2 > testContrast2) ? + baselineContrast2 : testContrast2; + + // Avoid divides by close to zero + if (denominator < 1e-5f) { + denominator = 1e-5f; + } + contrast[levelIndex] = numerator / denominator; + contrastSum += contrast[levelIndex]; + } + + if (contrastSum < 1e-5f) { + contrastSum = 1e-5f; + } + + float F = 0.0f; + for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) { + float contrastSensitivity = contrastSensitivityTable[levelIndex * 1000 + + (int)(lAdapt * 10.0)]; + float mask = SkPMetricUtil::get_visual_mask(contrast[levelIndex] * + contrastSensitivity); + + F += contrast[levelIndex] + + thresholdFactorFrequency[levelIndex] * mask / contrastSum; + } + + if (F < 1.0f) { + F = 1.0f; + } + + if (F > 10.0f) { + F = 10.0f; + } + + + bool isFailure = false; + if (fabsf(lBaseline - lTest) > F * SkPMetricUtil::get_threshold_vs_intensity(lAdapt)) { + isFailure = true; + } else { + LAB baselineColor; + LAB testColor; + baselineLAB->readPixel(x, y, &baselineColor); + testLAB->readPixel(x, y, &testColor); + float contrastA = baselineColor.a - testColor.a; + float contrastB = baselineColor.b - testColor.b; + float colorScale = 1.0f; + if (lAdapt < 10.0f) { + colorScale = lAdapt / 10.0f; + } + colorScale *= colorScale; + + if ((contrastA * contrastA + contrastB * contrastB) * colorScale > F) + { + isFailure = true; + } + } + + if (isFailure) { + (*poiCount)++; + } + } + } + + SkDELETE_ARRAY(cyclesPerDegree); + SkDELETE_ARRAY(contrast); + SkDELETE_ARRAY(thresholdFactorFrequency); + SkDELETE_ARRAY(contrastSensitivityTable); + return 1.0 - (double)(*poiCount) / (width * height); +} + +bool SkPMetric::diff(SkBitmap* baseline, SkBitmap* test, bool computeMask, Result* result) const { + double startTime = get_seconds(); + + // Ensure the images are comparable + if (baseline->width() != test->width() || baseline->height() != test->height() || + baseline->width() <= 0 || baseline->height() <= 0) { + return false; + } + + ImageLAB baselineLAB(baseline->width(), baseline->height()); + ImageLAB testLAB(baseline->width(), baseline->height()); + + if (!bitmap_to_cielab(baseline, &baselineLAB) || !bitmap_to_cielab(test, &testLAB)) { + return true; + } + + result->poiCount = 0; + result->result = pmetric(&baselineLAB, &testLAB, &result->poiCount); + result->timeElapsed = get_seconds() - startTime; + + return true; +} diff --git a/chromium/third_party/skia/tools/skpdiff/SkPMetric.h b/chromium/third_party/skia/tools/skpdiff/SkPMetric.h new file mode 100644 index 00000000000..b60858a6597 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/SkPMetric.h @@ -0,0 +1,30 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPMetric_DEFINED +#define SkPMetric_DEFINED + +#include "SkTArray.h" +#include "SkTDArray.h" + +#include "SkImageDiffer.h" + +/** + * An image differ that uses the pdiff image metric to compare images. + */ +class SkPMetric : public SkImageDiffer { +public: + virtual const char* getName() const SK_OVERRIDE { return "perceptual"; } + virtual bool diff(SkBitmap* baseline, SkBitmap* test, bool computeMask, + Result* result) const SK_OVERRIDE; + +private: + typedef SkImageDiffer INHERITED; +}; + + +#endif diff --git a/chromium/third_party/skia/tools/skpdiff/SkPMetricUtil_generated.h b/chromium/third_party/skia/tools/skpdiff/SkPMetricUtil_generated.h new file mode 100644 index 00000000000..8abd5957686 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/SkPMetricUtil_generated.h @@ -0,0 +1,2586 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +// To regenerate SkPMetricUtil_generated.h, simply run ./generate_pmetric_tables.py +namespace SkPMetricUtil { +static float gCubeRootTable[] = { + 0.0000000000f,0.0992125657f,0.1250000000f,0.1430892803f,0.1574901312f,0.1696511010f, + 0.1802811963f,0.1897868107f,0.1984251315f,0.2063704531f,0.2137469933f,0.2206467710f, + 0.2271400741f,0.2332819473f,0.2391163978f,0.2446792276f,0.2500000000f,0.2551034439f, + 0.2600104779f,0.2647389740f,0.2693043363f,0.2737199462f,0.2779975113f,0.2821473397f, + 0.2861785606f,0.2900993021f,0.2939168360f,0.2976376972f,0.3012677830f,0.3048124351f, + 0.3082765093f,0.3116644345f,0.3149802625f,0.3182277106f,0.3214101988f,0.3245308814f, + 0.3275926743f,0.3305982795f,0.3335502061f,0.3364507883f,0.3393022021f,0.3421064797f, + 0.3448655220f,0.3475811104f,0.3502549163f,0.3528885108f,0.3554833725f,0.3580408947f, + 0.3605623926f,0.3630491083f,0.3655022173f,0.3679228323f,0.3703120086f,0.3726707474f, + 0.3750000000f,0.3773006710f,0.3795736215f,0.3818196715f,0.3840396032f,0.3862341629f, + 0.3884040632f,0.3905499856f,0.3926725815f,0.3947724749f,0.3968502630f,0.3989065187f, + 0.4009417912f,0.4029566080f,0.4049514752f,0.4069268795f,0.4088832888f,0.4108211532f, + 0.4127409061f,0.4146429648f,0.4165277315f,0.4183955938f,0.4202469258f,0.4220820885f, + 0.4239014304f,0.4257052880f,0.4274939867f,0.4292678410f,0.4310271550f,0.4327722233f, + 0.4345033306f,0.4362207529f,0.4379247575f,0.4396156035f,0.4412935419f,0.4429588163f, + 0.4446116631f,0.4462523114f,0.4478809839f,0.4494978968f,0.4511032600f,0.4526972777f, + 0.4542801482f,0.4558520644f,0.4574132138f,0.4589637789f,0.4605039373f,0.4620338620f, + 0.4635537212f,0.4650636788f,0.4665638946f,0.4680545242f,0.4695357193f,0.4710076277f, + 0.4724703937f,0.4739241579f,0.4753690576f,0.4768052266f,0.4782327957f,0.4796518924f, + 0.4810626414f,0.4824651643f,0.4838595801f,0.4852460048f,0.4866245520f,0.4879953326f, + 0.4893584551f,0.4907140257f,0.4920621479f,0.4934029233f,0.4947364512f,0.4960628287f, + 0.4973821510f,0.4986945110f,0.5000000000f,0.5012987071f,0.5025907198f,0.5038761237f, + 0.5051550026f,0.5064274387f,0.5076935126f,0.5089533031f,0.5102068877f,0.5114543423f, + 0.5126957412f,0.5139311574f,0.5151606625f,0.5163843266f,0.5176022187f,0.5188144063f, + 0.5200209558f,0.5212219322f,0.5224173995f,0.5236074205f,0.5247920567f,0.5259713687f, + 0.5271454158f,0.5283142565f,0.5294779480f,0.5306365468f,0.5317901081f,0.5329386865f, + 0.5340823352f,0.5352211070f,0.5363550534f,0.5374842252f,0.5386086725f,0.5397284443f, + 0.5408435889f,0.5419541538f,0.5430601857f,0.5441617307f,0.5452588339f,0.5463515399f, + 0.5474398925f,0.5485239347f,0.5496037090f,0.5506792572f,0.5517506203f,0.5528178389f, + 0.5538809527f,0.5549400011f,0.5559950226f,0.5570460554f,0.5580931369f,0.5591363041f, + 0.5601755933f,0.5612110404f,0.5622426807f,0.5632705489f,0.5642946794f,0.5653151060f, + 0.5663318620f,0.5673449802f,0.5683544930f,0.5693604322f,0.5703628294f,0.5713617156f, + 0.5723571213f,0.5733490767f,0.5743376115f,0.5753227552f,0.5763045365f,0.5772829841f, + 0.5782581261f,0.5792299904f,0.5801986042f,0.5811639947f,0.5821261885f,0.5830852119f, + 0.5840410910f,0.5849938514f,0.5859435185f,0.5868901171f,0.5878336719f,0.5887742074f, + 0.5897117475f,0.5906463161f,0.5915779364f,0.5925066317f,0.5934324248f,0.5943553383f, + 0.5952753945f,0.5961926153f,0.5971070226f,0.5980186378f,0.5989274821f,0.5998335765f, + 0.6007369417f,0.6016375981f,0.6025355660f,0.6034308654f,0.6043235159f,0.6052135371f, + 0.6061009482f,0.6069857684f,0.6078680164f,0.6087477108f,0.6096248701f,0.6104995125f, + 0.6113716558f,0.6122413179f,0.6131085164f,0.6139732687f,0.6148355918f,0.6156955028f, + 0.6165530186f,0.6174081556f,0.6182609304f,0.6191113592f,0.6199594580f,0.6208052427f, + 0.6216487292f,0.6224899328f,0.6233288690f,0.6241655531f,0.6250000000f,0.6258322247f, + 0.6266622419f,0.6274900661f,0.6283157119f,0.6291391935f,0.6299605249f,0.6307797203f, + 0.6315967934f,0.6324117579f,0.6332246274f,0.6340354152f,0.6348441348f,0.6356507991f, + 0.6364554212f,0.6372580140f,0.6380585903f,0.6388571625f,0.6396537434f,0.6404483451f, + 0.6412409800f,0.6420316602f,0.6428203977f,0.6436072043f,0.6443920919f,0.6451750722f, + 0.6459561565f,0.6467353565f,0.6475126834f,0.6482881485f,0.6490617627f,0.6498335373f, + 0.6506034829f,0.6513716105f,0.6521379308f,0.6529024543f,0.6536651915f,0.6544261528f, + 0.6551853486f,0.6559427889f,0.6566984841f,0.6574524439f,0.6582046785f,0.6589551976f, + 0.6597040110f,0.6604511283f,0.6611965591f,0.6619403129f,0.6626823990f,0.6634228269f, + 0.6641616057f,0.6648987446f,0.6656342527f,0.6663681389f,0.6671004122f,0.6678310813f, + 0.6685601552f,0.6692876423f,0.6700135514f,0.6707378909f,0.6714606694f,0.6721818951f, + 0.6729015765f,0.6736197217f,0.6743363390f,0.6750514364f,0.6757650220f,0.6764771037f, + 0.6771876894f,0.6778967869f,0.6786044041f,0.6793105487f,0.6800152282f,0.6807184502f, + 0.6814202223f,0.6821205519f,0.6828194464f,0.6835169131f,0.6842129593f,0.6849075923f, + 0.6856008191f,0.6862926468f,0.6869830825f,0.6876721332f,0.6883598058f,0.6890461072f, + 0.6897310441f,0.6904146234f,0.6910968517f,0.6917777357f,0.6924572821f,0.6931354973f, + 0.6938123879f,0.6944879602f,0.6951622208f,0.6958351760f,0.6965068319f,0.6971771950f, + 0.6978462715f,0.6985140673f,0.6991805888f,0.6998458419f,0.7005098327f,0.7011725671f, + 0.7018340510f,0.7024942904f,0.7031532910f,0.7038110588f,0.7044675993f,0.7051229184f, + 0.7057770217f,0.7064299147f,0.7070816032f,0.7077320927f,0.7083813885f,0.7090294963f, + 0.7096764214f,0.7103221691f,0.7109667450f,0.7116101541f,0.7122524019f,0.7128934935f, + 0.7135334342f,0.7141722290f,0.7148098831f,0.7154464016f,0.7160817895f,0.7167160518f, + 0.7173491935f,0.7179812196f,0.7186121349f,0.7192419442f,0.7198706526f,0.7204982646f, + 0.7211247852f,0.7217502189f,0.7223745706f,0.7229978450f,0.7236200465f,0.7242411799f, + 0.7248612497f,0.7254802605f,0.7260982167f,0.7267151229f,0.7273309834f,0.7279458028f, + 0.7285595854f,0.7291723356f,0.7297840576f,0.7303947558f,0.7310044346f,0.7316130980f, + 0.7322207503f,0.7328273958f,0.7334330386f,0.7340376827f,0.7346413324f,0.7352439917f, + 0.7358456646f,0.7364463552f,0.7370460675f,0.7376448054f,0.7382425730f,0.7388393740f, + 0.7394352125f,0.7400300922f,0.7406240171f,0.7412169909f,0.7418090175f,0.7424001007f, + 0.7429902441f,0.7435794515f,0.7441677266f,0.7447550732f,0.7453414947f,0.7459269950f, + 0.7465115775f,0.7470952459f,0.7476780037f,0.7482598545f,0.7488408019f,0.7494208492f, + 0.7500000000f,0.7505782577f,0.7511556259f,0.7517321078f,0.7523077069f,0.7528824265f, + 0.7534562700f,0.7540292408f,0.7546013421f,0.7551725772f,0.7557429495f,0.7563124621f, + 0.7568811183f,0.7574489213f,0.7580158743f,0.7585819805f,0.7591472430f,0.7597116649f, + 0.7602752494f,0.7608379997f,0.7613999186f,0.7619610094f,0.7625212750f,0.7630807186f, + 0.7636393430f,0.7641971514f,0.7647541466f,0.7653103316f,0.7658657094f,0.7664202829f, + 0.7669740550f,0.7675270285f,0.7680792064f,0.7686305915f,0.7691811867f,0.7697309947f, + 0.7702800185f,0.7708282607f,0.7713757241f,0.7719224115f,0.7724683257f,0.7730134694f, + 0.7735578453f,0.7741014560f,0.7746443043f,0.7751863929f,0.7757277244f,0.7762683013f, + 0.7768081265f,0.7773472024f,0.7778855317f,0.7784231168f,0.7789599605f,0.7794960653f, + 0.7800314336f,0.7805660681f,0.7810999712f,0.7816331454f,0.7821655932f,0.7826973171f, + 0.7832283195f,0.7837586029f,0.7842881697f,0.7848170223f,0.7853451631f,0.7858725945f, + 0.7863993189f,0.7869253387f,0.7874506562f,0.7879752737f,0.7884991936f,0.7890224182f, + 0.7895449497f,0.7900667905f,0.7905879429f,0.7911084091f,0.7916281914f,0.7921472920f, + 0.7926657132f,0.7931834571f,0.7937005260f,0.7942169220f,0.7947326475f,0.7952477044f, + 0.7957620951f,0.7962758215f,0.7967888860f,0.7973012906f,0.7978130374f,0.7983241285f, + 0.7988345660f,0.7993443521f,0.7998534888f,0.8003619781f,0.8008698221f,0.8013770229f, + 0.8018835825f,0.8023895029f,0.8028947861f,0.8033994341f,0.8039034489f,0.8044068326f, + 0.8049095870f,0.8054117141f,0.8059132159f,0.8064140944f,0.8069143514f,0.8074139889f, + 0.8079130088f,0.8084114130f,0.8089092034f,0.8094063819f,0.8099029503f,0.8103989106f, + 0.8108942646f,0.8113890141f,0.8118831610f,0.8123767071f,0.8128696543f,0.8133620043f, + 0.8138537589f,0.8143449200f,0.8148354894f,0.8153254688f,0.8158148599f,0.8163036646f, + 0.8167918846f,0.8172795217f,0.8177665775f,0.8182530539f,0.8187389525f,0.8192242751f, + 0.8197090233f,0.8201931988f,0.8206768034f,0.8211598387f,0.8216423064f,0.8221242082f, + 0.8226055457f,0.8230863205f,0.8235665343f,0.8240461888f,0.8245252855f,0.8250038261f, + 0.8254818122f,0.8259592454f,0.8264361273f,0.8269124595f,0.8273882435f,0.8278634810f, + 0.8283381734f,0.8288123225f,0.8292859296f,0.8297589964f,0.8302315244f,0.8307035152f, + 0.8311749701f,0.8316458909f,0.8321162790f,0.8325861358f,0.8330554629f,0.8335242618f, + 0.8339925340f,0.8344602809f,0.8349275040f,0.8353942047f,0.8358603846f,0.8363260451f, + 0.8367911876f,0.8372558136f,0.8377199244f,0.8381835216f,0.8386466065f,0.8391091806f, + 0.8395712452f,0.8400328018f,0.8404938517f,0.8409543964f,0.8414144372f,0.8418739754f, + 0.8423330126f,0.8427915500f,0.8432495890f,0.8437071309f,0.8441641771f,0.8446207289f, + 0.8450767877f,0.8455323548f,0.8459874315f,0.8464420192f,0.8468961190f,0.8473497325f, + 0.8478028608f,0.8482555052f,0.8487076671f,0.8491593476f,0.8496105482f,0.8500612701f, + 0.8505115145f,0.8509612827f,0.8514105760f,0.8518593955f,0.8523077427f,0.8527556186f, + 0.8532030246f,0.8536499618f,0.8540964315f,0.8545424350f,0.8549879733f,0.8554330478f, + 0.8558776597f,0.8563218101f,0.8567655002f,0.8572087313f,0.8576515045f,0.8580938210f, + 0.8585356819f,0.8589770885f,0.8594180419f,0.8598585433f,0.8602985938f,0.8607381946f, + 0.8611773468f,0.8616160516f,0.8620543101f,0.8624921234f,0.8629294927f,0.8633664191f, + 0.8638029038f,0.8642389477f,0.8646745521f,0.8651097180f,0.8655444466f,0.8659787389f, + 0.8664125961f,0.8668460191f,0.8672790092f,0.8677115674f,0.8681436948f,0.8685753923f, + 0.8690066612f,0.8694375025f,0.8698679171f,0.8702979063f,0.8707274710f,0.8711566122f, + 0.8715853311f,0.8720136286f,0.8724415059f,0.8728689638f,0.8732960035f,0.8737226259f, + 0.8741488321f,0.8745746232f,0.8750000000f,0.8754249636f,0.8758495151f,0.8762736554f, + 0.8766973854f,0.8771207063f,0.8775436190f,0.8779661244f,0.8783882235f,0.8788099174f, + 0.8792312070f,0.8796520932f,0.8800725771f,0.8804926595f,0.8809123415f,0.8813316240f, + 0.8817505079f,0.8821689942f,0.8825870838f,0.8830047777f,0.8834220768f,0.8838389821f, + 0.8842554944f,0.8846716147f,0.8850873439f,0.8855026829f,0.8859176327f,0.8863321941f, + 0.8867463681f,0.8871601555f,0.8875735573f,0.8879865744f,0.8883992076f,0.8888114579f, + 0.8892233261f,0.8896348131f,0.8900459199f,0.8904566472f,0.8908669959f,0.8912769670f, + 0.8916865612f,0.8920957795f,0.8925046227f,0.8929130917f,0.8933211874f,0.8937289105f, + 0.8941362619f,0.8945432425f,0.8949498531f,0.8953560946f,0.8957619678f,0.8961674735f, + 0.8965726125f,0.8969773858f,0.8973817940f,0.8977858381f,0.8981895188f,0.8985928370f, + 0.8989957935f,0.8993983891f,0.8998006246f,0.9002025007f,0.9006040184f,0.9010051784f, + 0.9014059814f,0.9018064284f,0.9022065200f,0.9026062571f,0.9030056405f,0.9034046709f, + 0.9038033491f,0.9042016758f,0.9045996520f,0.9049972783f,0.9053945554f,0.9057914843f, + 0.9061880655f,0.9065843000f,0.9069801884f,0.9073757315f,0.9077709300f,0.9081657847f, + 0.9085602964f,0.9089544658f,0.9093482936f,0.9097417806f,0.9101349275f,0.9105277350f, + 0.9109202039f,0.9113123349f,0.9117041287f,0.9120955861f,0.9124867078f,0.9128774944f, + 0.9132679468f,0.9136580655f,0.9140478514f,0.9144373052f,0.9148264275f,0.9152152191f, + 0.9156036806f,0.9159918127f,0.9163796163f,0.9167670918f,0.9171542402f,0.9175410619f, + 0.9179275578f,0.9183137284f,0.9186995746f,0.9190850969f,0.9194702961f,0.9198551727f, + 0.9202397276f,0.9206239614f,0.9210078747f,0.9213914682f,0.9217747425f,0.9221576984f, + 0.9225403365f,0.9229226575f,0.9233046619f,0.9236863505f,0.9240677240f,0.9244487829f, + 0.9248295279f,0.9252099596f,0.9255900788f,0.9259698860f,0.9263493819f,0.9267285671f, + 0.9271074423f,0.9274860081f,0.9278642651f,0.9282422139f,0.9286198552f,0.9289971896f, + 0.9293742177f,0.9297509402f,0.9301273576f,0.9305034706f,0.9308792798f,0.9312547858f, + 0.9316299892f,0.9320048907f,0.9323794907f,0.9327537901f,0.9331277892f,0.9335014888f, + 0.9338748894f,0.9342479917f,0.9346207962f,0.9349933035f,0.9353655143f,0.9357374291f, + 0.9361090484f,0.9364803729f,0.9368514032f,0.9372221399f,0.9375925834f,0.9379627345f, + 0.9383325937f,0.9387021615f,0.9390714386f,0.9394404254f,0.9398091226f,0.9401775308f, + 0.9405456505f,0.9409134822f,0.9412810266f,0.9416482841f,0.9420152554f,0.9423819410f, + 0.9427483415f,0.9431144574f,0.9434802893f,0.9438458377f,0.9442111031f,0.9445760862f, + 0.9449407874f,0.9453052074f,0.9456693465f,0.9460332055f,0.9463967848f,0.9467600849f, + 0.9471231065f,0.9474858499f,0.9478483159f,0.9482105048f,0.9485724172f,0.9489340537f, + 0.9492954148f,0.9496565009f,0.9500173127f,0.9503778506f,0.9507381152f,0.9510981069f, + 0.9514578264f,0.9518172740f,0.9521764504f,0.9525353560f,0.9528939913f,0.9532523569f, + 0.9536104532f,0.9539682808f,0.9543258402f,0.9546831318f,0.9550401562f,0.9553969139f, + 0.9557534053f,0.9561096310f,0.9564655914f,0.9568212871f,0.9571767185f,0.9575318861f, + 0.9578867905f,0.9582414321f,0.9585958113f,0.9589499288f,0.9593037849f,0.9596573801f, + 0.9600107149f,0.9603637899f,0.9607166054f,0.9610691620f,0.9614214601f,0.9617735002f, + 0.9621252828f,0.9624768083f,0.9628280773f,0.9631790901f,0.9635298473f,0.9638803492f, + 0.9642305965f,0.9645805895f,0.9649303287f,0.9652798145f,0.9656290475f,0.9659780280f, + 0.9663267566f,0.9666752336f,0.9670234596f,0.9673714350f,0.9677191602f,0.9680666356f, + 0.9684138619f,0.9687608393f,0.9691075683f,0.9694540494f,0.9698002830f,0.9701462696f, + 0.9704920096f,0.9708375034f,0.9711827515f,0.9715277542f,0.9718725122f,0.9722170257f, + 0.9725612952f,0.9729053211f,0.9732491040f,0.9735926441f,0.9739359420f,0.9742789980f, + 0.9746218126f,0.9749643862f,0.9753067192f,0.9756488121f,0.9759906652f,0.9763322791f, + 0.9766736540f,0.9770147905f,0.9773556889f,0.9776963497f,0.9780367732f,0.9783769600f, + 0.9787169103f,0.9790566246f,0.9793961034f,0.9797353469f,0.9800743557f,0.9804131302f, + 0.9807516706f,0.9810899776f,0.9814280513f,0.9817658923f,0.9821035010f,0.9824408777f, + 0.9827780228f,0.9831149368f,0.9834516200f,0.9837880729f,0.9841242958f,0.9844602891f, + 0.9847960532f,0.9851315885f,0.9854668954f,0.9858019743f,0.9861368255f,0.9864714495f, + 0.9868058466f,0.9871400173f,0.9874739618f,0.9878076807f,0.9881411742f,0.9884744427f, + 0.9888074867f,0.9891403065f,0.9894729024f,0.9898052749f,0.9901374244f,0.9904693511f, + 0.9908010556f,0.9911325380f,0.9914637989f,0.9917948386f,0.9921256575f,0.9924562559f, + 0.9927866341f,0.9931167927f,0.9934467318f,0.9937764520f,0.9941059535f,0.9944352367f, + 0.9947643020f,0.9950931497f,0.9954217802f,0.9957501939f,0.9960783911f,0.9964063721f, + 0.9967341374f,0.9970616873f,0.9973890221f,0.9977161421f,0.9980430478f,0.9983697395f, + 0.9986962176f,0.9990224823f,0.9993485340f,0.9996743731f, +}; +static float get_cube_root(float value) { + SkASSERT(value >= 0.0f); + SkASSERT(value * 1023.0f < 1024.0f); + return gCubeRootTable[(int)(value * 1023.0f)]; +} + +static float gGammaTable[] = { + 0.0000000000f,0.0000050771f,0.0000233280f,0.0000569218f,0.0001071874f,0.0001751240f, + 0.0002615438f,0.0003671363f,0.0004925038f,0.0006381828f,0.0008046585f,0.0009923743f, + 0.0012017395f,0.0014331346f,0.0016869153f,0.0019634162f,0.0022629532f,0.0025858256f, + 0.0029323183f,0.0033027030f,0.0036972396f,0.0041161771f,0.0045597549f,0.0050282035f, + 0.0055217449f,0.0060405937f,0.0065849574f,0.0071550370f,0.0077510274f,0.0083731177f, + 0.0090214919f,0.0096963287f,0.0103978023f,0.0111260824f,0.0118813344f,0.0126637200f, + 0.0134733969f,0.0143105194f,0.0151752382f,0.0160677009f,0.0169880521f,0.0179364333f, + 0.0189129834f,0.0199178384f,0.0209511319f,0.0220129949f,0.0231035562f,0.0242229421f, + 0.0253712769f,0.0265486828f,0.0277552800f,0.0289911865f,0.0302565189f,0.0315513914f, + 0.0328759169f,0.0342302066f,0.0356143697f,0.0370285142f,0.0384727463f,0.0399471710f, + 0.0414518916f,0.0429870102f,0.0445526273f,0.0461488424f,0.0477757536f,0.0494334576f, + 0.0511220501f,0.0528416255f,0.0545922773f,0.0563740976f,0.0581871775f,0.0600316071f, + 0.0619074756f,0.0638148709f,0.0657538803f,0.0677245897f,0.0697270844f,0.0717614488f, + 0.0738277663f,0.0759261195f,0.0780565900f,0.0802192587f,0.0824142059f,0.0846415107f, + 0.0869012518f,0.0891935069f,0.0915183530f,0.0938758665f,0.0962661231f,0.0986891975f, + 0.1011451642f,0.1036340967f,0.1061560678f,0.1087111500f,0.1112994148f,0.1139209334f, + 0.1165757762f,0.1192640130f,0.1219857132f,0.1247409454f,0.1275297778f,0.1303522781f, + 0.1332085132f,0.1360985497f,0.1390224537f,0.1419802907f,0.1449721256f,0.1479980230f, + 0.1510580469f,0.1541522608f,0.1572807279f,0.1604435107f,0.1636406715f,0.1668722719f, + 0.1701383732f,0.1734390363f,0.1767743216f,0.1801442892f,0.1835489985f,0.1869885088f, + 0.1904628788f,0.1939721670f,0.1975164314f,0.2010957296f,0.2047101188f,0.2083596560f, + 0.2120443975f,0.2157643996f,0.2195197181f,0.2233104083f,0.2271365255f,0.2309981243f, + 0.2348952592f,0.2388279843f,0.2427963533f,0.2468004196f,0.2508402364f,0.2549158566f, + 0.2590273325f,0.2631747164f,0.2673580602f,0.2715774154f,0.2758328335f,0.2801243653f, + 0.2844520616f,0.2888159728f,0.2932161491f,0.2976526404f,0.3021254964f,0.3066347662f, + 0.3111804991f,0.3157627437f,0.3203815488f,0.3250369625f,0.3297290330f,0.3344578079f, + 0.3392233349f,0.3440256613f,0.3488648341f,0.3537409001f,0.3586539059f,0.3636038979f, + 0.3685909222f,0.3736150246f,0.3786762509f,0.3837746465f,0.3889102565f,0.3940831261f, + 0.3992932999f,0.4045408226f,0.4098257384f,0.4151480917f,0.4205079262f,0.4259052857f, + 0.4313402138f,0.4368127538f,0.4423229488f,0.4478708418f,0.4534564755f,0.4590798924f, + 0.4647411350f,0.4704402453f,0.4761772654f,0.4819522371f,0.4877652019f,0.4936162013f, + 0.4995052766f,0.5054324688f,0.5113978189f,0.5174013675f,0.5234431552f,0.5295232224f, + 0.5356416093f,0.5417983560f,0.5479935022f,0.5542270878f,0.5604991522f,0.5668097349f, + 0.5731588751f,0.5795466118f,0.5859729839f,0.5924380303f,0.5989417895f,0.6054842999f, + 0.6120655999f,0.6186857275f,0.6253447208f,0.6320426176f,0.6387794557f,0.6455552724f, + 0.6523701054f,0.6592239918f,0.6661169688f,0.6730490733f,0.6800203422f,0.6870308122f, + 0.6940805198f,0.7011695015f,0.7082977937f,0.7154654323f,0.7226724536f,0.7299188934f, + 0.7372047874f,0.7445301713f,0.7518950806f,0.7592995507f,0.7667436169f,0.7742273142f, + 0.7817506778f,0.7893137424f,0.7969165429f,0.8045591139f,0.8122414899f,0.8199637053f, + 0.8277257945f,0.8355277915f,0.8433697304f,0.8512516452f,0.8591735697f,0.8671355375f, + 0.8751375824f,0.8831797377f,0.8912620368f,0.8993845130f,0.9075471995f,0.9157501293f, + 0.9239933353f,0.9322768503f,0.9406007070f,0.9489649382f,0.9573695762f,0.9658146535f, + 0.9743002024f,0.9828262551f,0.9913928436f,1.0000000000f, +}; +static float get_gamma(unsigned char value) { + return gGammaTable[value]; +} + +static float gTVITable[] = { + 0.0013803843f,0.0054723435f,0.0090762146f,0.0127002285f,0.0161086814f,0.0201358517f, + 0.0241630221f,0.0281901924f,0.0322173627f,0.0362445331f,0.0402717034f,0.0442988738f, + 0.0483260441f,0.0523532145f,0.0563803848f,0.0604075551f,0.0644347255f,0.0684618958f, + 0.0724890662f,0.0765162365f,0.0805434069f,0.0845705772f,0.0885977476f,0.0926249179f, + 0.0966520882f,0.1006792586f,0.1047064289f,0.1087335993f,0.1127607696f,0.1167879400f, + 0.1208151103f,0.1248422806f,0.1288694510f,0.1328966213f,0.1369237917f,0.1409509620f, + 0.1449781324f,0.1490053027f,0.1530324730f,0.1570596434f,0.1610868137f,0.1651139841f, + 0.1691411544f,0.1731683248f,0.1771954951f,0.1812226654f,0.1852498358f,0.1892770061f, + 0.1933041765f,0.1973313468f,0.2013585172f,0.2053856875f,0.2094128578f,0.2134400282f, + 0.2174671985f,0.2214943689f,0.2255215392f,0.2295487096f,0.2335758799f,0.2376030503f, + 0.2416302206f,0.2456573909f,0.2496845613f,0.2537117316f,0.2577389020f,0.2617660723f, + 0.2657932427f,0.2698204130f,0.2738475833f,0.2778747537f,0.2819019240f,0.2859290944f, + 0.2899562647f,0.2939834351f,0.2980106054f,0.3020377757f,0.3060649461f,0.3100921164f, + 0.3141192868f,0.3181464571f,0.3221736275f,0.3262007978f,0.3302279681f,0.3342551385f, + 0.3382823088f,0.3423094792f,0.3463366495f,0.3503638199f,0.3543909902f,0.3584181605f, + 0.3624453309f,0.3664725012f,0.3704996716f,0.3745268419f,0.3785540123f,0.3825811826f, + 0.3862015808f,0.3874843130f,0.3887618285f,0.3900342072f,0.3913015271f,0.3925638643f, + 0.3938212928f,0.3950738850f,0.3963217116f,0.3975648414f,0.3988033417f,0.4000372783f, + 0.4012667154f,0.4024917157f,0.4037123404f,0.4049286496f,0.4061407017f,0.4073485541f, + 0.4085522627f,0.4097518824f,0.4109474667f,0.4121390682f,0.4133267382f,0.4145105269f, + 0.4156904836f,0.4168666565f,0.4180390927f,0.4192078385f,0.4203729393f,0.4215344394f, + 0.4226923823f,0.4238468107f,0.4249977665f,0.4261452906f,0.4272894232f,0.4284302038f, + 0.4295676711f,0.4307018630f,0.4318328169f,0.4329605692f,0.4340851559f,0.4352066122f, + 0.4363249726f,0.4374402712f,0.4385525412f,0.4396618156f,0.4407681264f,0.4418715052f, + 0.4429719831f,0.4440695907f,0.4451643580f,0.4462563143f,0.4473454889f,0.4484319100f, + 0.4495156058f,0.4505966038f,0.4516749311f,0.4527506143f,0.4538236798f,0.4548941532f, + 0.4559620600f,0.4570274252f,0.4580902732f,0.4591506283f,0.4602085144f,0.4612639548f, + 0.4623169726f,0.4633675905f,0.4644158309f,0.4654617159f,0.4665052671f,0.4675465059f, + 0.4685854533f,0.4696221301f,0.4706565566f,0.4716887532f,0.4727187394f,0.4737465350f, + 0.4747721591f,0.4757956308f,0.4768169688f,0.4778361914f,0.4788533170f,0.4798683633f, + 0.4808813482f,0.4818922891f,0.4829012030f,0.4839081071f,0.4849130180f,0.4859159522f, + 0.4869169260f,0.4879159554f,0.4889130563f,0.4899082444f,0.4909015350f,0.4918929433f, + 0.4928824844f,0.4938701732f,0.4948560241f,0.4958400518f,0.4968222703f,0.4978026939f, + 0.4987813364f,0.4997582115f,0.5007333328f,0.5017067136f,0.5026783672f,0.5036483066f, + 0.5046165447f,0.5055830942f,0.5065479677f,0.5075111777f,0.5084727363f,0.5094326557f, + 0.5103909478f,0.5113476246f,0.5123026977f,0.5132561786f,0.5142080788f,0.5151584096f, + 0.5161071821f,0.5170544073f,0.5180000961f,0.5189442593f,0.5198869076f,0.5208280514f, + 0.5217677012f,0.5227058673f,0.5236425598f,0.5245777888f,0.5255115642f,0.5264438959f, + 0.5273747936f,0.5283042669f,0.5292323254f,0.5301589784f,0.5310842352f,0.5320081051f, + 0.5329305972f,0.5338517205f,0.5347714839f,0.5356898962f,0.5366069661f,0.5375227023f, + 0.5384371133f,0.5393502076f,0.5402619936f,0.5411724795f,0.5420816735f,0.5429895837f, + 0.5438962181f,0.5448015848f,0.5457056915f,0.5466085461f,0.5475101562f,0.5484105295f, + 0.5493096736f,0.5502075958f,0.5511043036f,0.5519998044f,0.5528941054f,0.5537872138f, + 0.5546791367f,0.5555698811f,0.5564594540f,0.5573478624f,0.5582351130f,0.5591212127f, + 0.5600061681f,0.5608899860f,0.5617726729f,0.5626542353f,0.5635346797f,0.5644140125f, + 0.5652922401f,0.5661693686f,0.5670454044f,0.5679203536f,0.5687942223f,0.5696670167f, + 0.5705387425f,0.5714094060f,0.5722790128f,0.5731475689f,0.5740150800f,0.5748815519f, + 0.5757469902f,0.5766114006f,0.5774747887f,0.5783371600f,0.5791985199f,0.5800588740f, + 0.5809182275f,0.5817765859f,0.5826339544f,0.5834903383f,0.5843457428f,0.5852001731f, + 0.5860536342f,0.5869061312f,0.5877576691f,0.5886082530f,0.5894578877f,0.5903065783f, + 0.5911543294f,0.5920011460f,0.5928470329f,0.5936919946f,0.5945360361f,0.5953791619f, + 0.5962213766f,0.5970626848f,0.5979030911f,0.5987426000f,0.5995812159f,0.6004189433f, + 0.6012557866f,0.6020917502f,0.6029268383f,0.6037610553f,0.6045944054f,0.6054268928f, + 0.6062585218f,0.6070892966f,0.6079192211f,0.6087482996f,0.6095765360f,0.6104039345f, + 0.6112304990f,0.6120562334f,0.6128811418f,0.6137052280f,0.6145284960f,0.6153509494f, + 0.6161725922f,0.6169934282f,0.6178134611f,0.6186326946f,0.6194511325f,0.6202687784f, + 0.6210856359f,0.6219017088f,0.6227170005f,0.6235315147f,0.6243452548f,0.6251582244f, + 0.6259704271f,0.6267818661f,0.6275925450f,0.6284024673f,0.6292116362f,0.6300200551f, + 0.6308277274f,0.6316346564f,0.6324408454f,0.6332462976f,0.6340510163f,0.6348550047f, + 0.6356582660f,0.6364608033f,0.6372626199f,0.6380637188f,0.6388641032f,0.6396637761f, + 0.6404627406f,0.6412609997f,0.6420585565f,0.6428554140f,0.6436515750f,0.6444470427f, + 0.6452418199f,0.6460359096f,0.6468293146f,0.6476220378f,0.6484140821f,0.6492054503f, + 0.6499961452f,0.6507861697f,0.6515755264f,0.6523642183f,0.6531522479f,0.6539396181f, + 0.6547263316f,0.6555123910f,0.6562977990f,0.6570825582f,0.6578666713f,0.6586501410f, + 0.6594329698f,0.6602151602f,0.6609967149f,0.6617776365f,0.6625579273f,0.6633375901f, + 0.6641166272f,0.6648950411f,0.6656728344f,0.6664500094f,0.6672265686f,0.6680025145f, + 0.6687778494f,0.6695525757f,0.6703266958f,0.6711002120f,0.6718731268f,0.6726454424f, + 0.6734171611f,0.6741882853f,0.6749588172f,0.6757287592f,0.6764981134f,0.6772668821f, + 0.6780350675f,0.6788026719f,0.6795696975f,0.6803361464f,0.6811020209f,0.6818673230f, + 0.6826320549f,0.6833962189f,0.6841598169f,0.6849228512f,0.6856853237f,0.6864472367f, + 0.6872085921f,0.6879693920f,0.6887296385f,0.6894893336f,0.6902484794f,0.6910070778f, + 0.6917651308f,0.6925226404f,0.6932796087f,0.6940360375f,0.6947919289f,0.6955472847f, + 0.6963021069f,0.6970563974f,0.6978101581f,0.6985633910f,0.6993160978f,0.7000682805f, + 0.7008199410f,0.7015710810f,0.7023217025f,0.7030718072f,0.7038213970f,0.7045704738f, + 0.7053190392f,0.7060670951f,0.7068146432f,0.7075616854f,0.7083082234f,0.7090542590f, + 0.7097997938f,0.7105448296f,0.7112893682f,0.7120334112f,0.7127769604f,0.7135200174f, + 0.7142625840f,0.7150046617f,0.7157462523f,0.7164873574f,0.7172279787f,0.7179681178f, + 0.7187077763f,0.7194469559f,0.7201856581f,0.7209238845f,0.7216616368f,0.7223989166f, + 0.7231357254f,0.7238720647f,0.7246079362f,0.7253433413f,0.7260782817f,0.7268127589f, + 0.7275467743f,0.7282803296f,0.7290134261f,0.7297460655f,0.7304782492f,0.7312099786f, + 0.7319412554f,0.7326720809f,0.7334024566f,0.7341323839f,0.7348618644f,0.7355908994f, + 0.7363194903f,0.7370476387f,0.7377753459f,0.7385026133f,0.7392294424f,0.7399558344f, + 0.7406817909f,0.7414073132f,0.7421324026f,0.7428570606f,0.7435812885f,0.7443050876f, + 0.7450284593f,0.7457514049f,0.7464739258f,0.7471960233f,0.7479176987f,0.7486389533f, + 0.7493597885f,0.7500802055f,0.7508002056f,0.7515197900f,0.7522389602f,0.7529577174f, + 0.7536760627f,0.7543939976f,0.7551115232f,0.7558286407f,0.7565453515f,0.7572616568f, + 0.7579775578f,0.7586930557f,0.7594081517f,0.7601228471f,0.7608371430f,0.7615510407f, + 0.7622645414f,0.7629776462f,0.7636903563f,0.7644026729f,0.7651145972f,0.7658261304f, + 0.7665372735f,0.7672480278f,0.7679583944f,0.7686683744f,0.7693779691f,0.7700871794f, + 0.7707960066f,0.7715044518f,0.7722125160f,0.7729202005f,0.7736275063f,0.7743344344f, + 0.7750409861f,0.7757471624f,0.7764529643f,0.7771583930f,0.7778634496f,0.7785681350f, + 0.7792724504f,0.7799763969f,0.7806799755f,0.7813831871f,0.7820860330f,0.7827885141f, + 0.7834906315f,0.7841923862f,0.7848937792f,0.7855948115f,0.7862954843f,0.7869957984f, + 0.7876957549f,0.7883953547f,0.7890945990f,0.7897934887f,0.7904920247f,0.7911902081f, + 0.7918880398f,0.7925855209f,0.7932826523f,0.7939794349f,0.7946758698f,0.7953719578f, + 0.7960677000f,0.7967630973f,0.7974581506f,0.7981528610f,0.7988472292f,0.7995412563f, + 0.8002349432f,0.8009282908f,0.8016213001f,0.8023139719f,0.8030063072f,0.8036983069f, + 0.8043899718f,0.8050813030f,0.8057723013f,0.8064629675f,0.8071533026f,0.8078433075f, + 0.8085329830f,0.8092223301f,0.8099113495f,0.8106000423f,0.8112884091f,0.8119764510f, + 0.8126641688f,0.8133515632f,0.8140386353f,0.8147253858f,0.8154118155f,0.8160979254f, + 0.8167837163f,0.8174691889f,0.8181543441f,0.8188391829f,0.8195237058f,0.8202079139f, + 0.8208918079f,0.8215753887f,0.8222586569f,0.8229416136f,0.8236242594f,0.8243065951f, + 0.8249886216f,0.8256703397f,0.8263517501f,0.8270328536f,0.8277136511f,0.8283941432f, + 0.8290743309f,0.8297542147f,0.8304337956f,0.8311130743f,0.8317920516f,0.8324707281f, + 0.8331491048f,0.8338271823f,0.8345049614f,0.8351824428f,0.8358596273f,0.8365365157f, + 0.8372131086f,0.8378894068f,0.8385654111f,0.8392411222f,0.8399165407f,0.8405916676f, + 0.8412665033f,0.8419410488f,0.8426153047f,0.8432892716f,0.8439629504f,0.8446363418f, + 0.8453094464f,0.8459822649f,0.8466547981f,0.8473270466f,0.8479990112f,0.8486706925f, + 0.8493420912f,0.8500132081f,0.8506840438f,0.8513545989f,0.8520248742f,0.8526948703f, + 0.8533645880f,0.8540340278f,0.8547031905f,0.8553720767f,0.8560406871f,0.8567090223f, + 0.8573770831f,0.8580448700f,0.8587123837f,0.8593796249f,0.8600465942f,0.8607132923f, + 0.8613797197f,0.8620458772f,0.8627117654f,0.8633773850f,0.8640427364f,0.8647078205f, + 0.8653726378f,0.8660371889f,0.8667014745f,0.8673654952f,0.8680292516f,0.8686927443f, + 0.8693559740f,0.8700189412f,0.8706816466f,0.8713440908f,0.8720062744f,0.8726681979f, + 0.8733298621f,0.8739912674f,0.8746524145f,0.8753133040f,0.8759739365f,0.8766343125f, + 0.8772944327f,0.8779542976f,0.8786139079f,0.8792732640f,0.8799323667f,0.8805912164f, + 0.8812498137f,0.8819081593f,0.8825662536f,0.8832240973f,0.8838816909f,0.8845390350f, + 0.8851961302f,0.8858529770f,0.8865095759f,0.8871659276f,0.8878220326f,0.8884778913f, + 0.8891335045f,0.8897888727f,0.8904439963f,0.8910988759f,0.8917535121f,0.8924079054f, + 0.8930620564f,0.8937159656f,0.8943696335f,0.8950230607f,0.8956762476f,0.8963291949f, + 0.8969819031f,0.8976343726f,0.8982866040f,0.8989385979f,0.8995903547f,0.9002418749f, + 0.9008931592f,0.9015442079f,0.9021950217f,0.9028456010f,0.9034959463f,0.9041460582f, + 0.9047959371f,0.9054455836f,0.9060949981f,0.9067441812f,0.9073931334f,0.9080418551f, + 0.9086903468f,0.9093386091f,0.9099866425f,0.9106344474f,0.9112820243f,0.9119293737f, + 0.9125764961f,0.9132233920f,0.9138700618f,0.9145165061f,0.9151627253f,0.9158087199f, + 0.9164544904f,0.9171000373f,0.9177453609f,0.9183904619f,0.9190353406f,0.9196799976f, + 0.9203244333f,0.9209686481f,0.9216126425f,0.9222564171f,0.9228999722f,0.9235433083f, + 0.9241864259f,0.9248293254f,0.9254720073f,0.9261144720f,0.9267567200f,0.9273987517f, + 0.9280405676f,0.9286821682f,0.9293235538f,0.9299647249f,0.9306056820f,0.9312464255f, + 0.9318869558f,0.9325272734f,0.9331673788f,0.9338072722f,0.9344469543f,0.9350864254f, + 0.9357256859f,0.9363647363f,0.9370035770f,0.9376422085f,0.9382806311f,0.9389188453f, + 0.9395568515f,0.9401946501f,0.9408322416f,0.9414696263f,0.9421068047f,0.9427437773f, + 0.9433805443f,0.9440171063f,0.9446534636f,0.9452896166f,0.9459255658f,0.9465613116f, + 0.9471968544f,0.9478321945f,0.9484673324f,0.9491022685f,0.9497370032f,0.9503715369f, + 0.9510058700f,0.9516400028f,0.9522739359f,0.9529076695f,0.9535412041f,0.9541745400f, + 0.9548076778f,0.9554406176f,0.9560733600f,0.9567059053f,0.9573382540f,0.9579704063f, + 0.9586023627f,0.9592341236f,0.9598656893f,0.9604970603f,0.9611282369f,0.9617592194f, + 0.9623900083f,0.9630206040f,0.9636510067f,0.9642812170f,0.9649112351f,0.9655410614f, + 0.9661706964f,0.9668001403f,0.9674293936f,0.9680584565f,0.9686873296f,0.9693160130f, + 0.9699445073f,0.9705728128f,0.9712009297f,0.9718288586f,0.9724565997f,0.9730841534f, + 0.9737115200f,0.9743387000f,0.9749656936f,0.9755925013f,0.9762191234f,0.9768455601f, + 0.9774718120f,0.9780978793f,0.9787237623f,0.9793494615f,0.9799749772f,0.9806003097f, + 0.9812254593f,0.9818504265f,0.9824752115f,0.9830998147f,0.9837242364f,0.9843484770f, + 0.9849725368f,0.9855964161f,0.9862201153f,0.9868436347f,0.9874669747f,0.9880901356f, + 0.9887131177f,0.9893359213f,0.9899585468f,0.9905809946f,0.9912032648f,0.9918253579f, + 0.9924472742f,0.9930690141f,0.9936905777f,0.9943119656f,0.9949331779f,0.9955542150f, + 0.9961750773f,0.9967957650f,0.9974162785f,0.9980366181f,0.9986567840f,0.9992767767f, + 0.9998965965f,1.0005162436f,1.0011357183f,1.0017550210f,1.0023741520f,1.0029931117f, + 1.0036119002f,1.0042305179f,1.0048489652f,1.0054672423f,1.0060853496f,1.0067032873f, + 1.0073210557f,1.0079386552f,1.0085560861f,1.0091733486f,1.0097904431f,1.0104073699f, + 1.0110241292f,1.0116407213f,1.0122571467f,1.0128734055f,1.0134894980f,1.0141054246f, + 1.0147211855f,1.0153367811f,1.0159522116f,1.0165674773f,1.0171825786f,1.0177975156f, + 1.0184122888f,1.0190268983f,1.0196413445f,1.0202556276f,1.0208697480f,1.0214837059f, + 1.0220975016f,1.0227111355f,1.0233246077f,1.0239379185f,1.0245510684f,1.0251640574f, + 1.0257768860f,1.0263895543f,1.0270020627f,1.0276144115f,1.0282266008f,1.0288386311f, + 1.0294505026f,1.0300622154f,1.0306737701f,1.0312851667f,1.0318964055f,1.0325074870f, + 1.0331184112f,1.0337291785f,1.0343397891f,1.0349502434f,1.0355605416f,1.0361706839f, + 1.0367806707f,1.0373905021f,1.0380001785f,1.0386097001f,1.0392190672f,1.0398282800f, + 1.0404373389f,1.0410462440f,1.0416549956f,1.0422635941f,1.0428720396f,1.0434803324f, + 1.0440884727f,1.0446964609f,1.0453042972f,1.0459119818f,1.0465195150f,1.0471268971f, + 1.0477341283f,1.0483412088f,1.0489481389f,1.0495549189f,1.0501615490f,1.0507680295f, + 1.0513743606f,1.0519805426f,1.0525865757f,1.0531924601f,1.0537981962f,1.0544037841f, + 1.0550092241f,1.0556145164f,1.0562196614f,1.0568246592f,1.0574295101f,1.0580342143f, + 1.0586387721f,1.0592431836f,1.0598474493f,1.0604515692f,1.0610555437f,1.0616593729f, + 1.0622630571f,1.0628665966f,1.0634699916f,1.0640732423f,1.0646763490f,1.0652793118f, + 1.0658821311f,1.0664848070f,1.0670873399f,1.0676897299f,1.0682919772f,1.0688940822f, + 1.0694960449f,1.0700978658f,1.0706995449f,1.0713010825f,1.0719024789f,1.0725037343f, + 1.0731048488f,1.0737058228f,1.0743066565f,1.0749073500f,1.0755079037f,1.0761083177f, + 1.0767085922f,1.0773087275f,1.0779087239f,1.0785085815f,1.0791083005f,1.0797078812f, + 1.0803073238f,1.0809066285f,1.0815057956f,1.0821048252f,1.0827037176f,1.0833024729f, + 1.0839010915f,1.0844995735f,1.0850979192f,1.0856961287f,1.0862942023f,1.0868921402f, + 1.0874899426f,1.0880876098f,1.0886851418f,1.0892825391f,1.0898798016f,1.0904769298f, + 1.0910739237f,1.0916707836f,1.0922675098f,1.0928641023f,1.0934605615f,1.0940568875f, + 1.0946530806f,1.0952491409f,1.0958450686f,1.0964408640f,1.0970365273f,1.0976320587f, + 1.0982274584f,1.0988227265f,1.0994178634f,1.1000128691f,1.1006077439f,1.1012024881f, + 1.1017971017f,1.1023915850f,1.1029859383f,1.1035801616f,1.1041742553f,1.1047682195f, + 1.1053620544f,1.1059557602f,1.1065493371f,1.1071427853f,1.1077361050f,1.1083292964f, + 1.1089223597f,1.1095152951f,1.1101081027f,1.1107007829f,1.1112933357f,1.1118857614f, + 1.1124780602f,1.1130702322f,1.1136622777f,1.1142541968f,1.1148459898f,1.1154376568f, + 1.1160291980f,1.1166206136f,1.1172119038f,1.1178030688f,1.1183941088f,1.1189850239f, + 1.1195758144f,1.1201664805f,1.1207570223f,1.1213474399f,1.1219377337f,1.1225279038f, + 1.1231179504f,1.1237078736f,1.1242976737f,1.1248873508f,1.1254769051f,1.1260663368f, + 1.1266556461f,1.1272448332f,1.1278338982f,1.1284228413f,1.1290116627f,1.1296003627f, + 1.1301889413f,1.1307773987f,1.1313657352f,1.1319539509f,1.1325420460f,1.1331300207f, + 1.1337178751f,1.1343056094f,1.1348932238f,1.1354807185f,1.1360680937f,1.1366553495f, + 1.1372424860f,1.1378295036f,1.1384164024f,1.1390031824f,1.1395898440f,1.1401763872f, + 1.1407628123f,1.1413491194f,1.1419353088f,1.1425213805f,1.1431073347f,1.1436931716f, + 1.1442788914f,1.1448644943f,1.1454499804f,1.1460353499f,1.1466206030f,1.1472057398f, + 1.1477907605f,1.1483756652f,1.1489604542f,1.1495451276f,1.1501296856f,1.1507141283f, + 1.1512984559f,1.1518826686f,1.1524667665f,1.1530507498f,1.1536346187f,1.1542183733f, + 1.1548020138f,1.1553855404f,1.1559689532f,1.1565522524f,1.1571354381f,1.1577185105f, + 1.1583014698f,1.1588843162f,1.1594670497f,1.1600496706f,1.1606321790f,1.1612145751f, + 1.1617968590f,1.1623790309f,1.1629610909f,1.1635430393f,1.1641248762f,1.1647066016f, + 1.1652882159f,1.1658697191f,1.1664511114f,1.1670323930f,1.1676135639f,1.1681946245f, + 1.1687755748f,1.1693564149f,1.1699371451f,1.1705177655f,1.1710982762f,1.1716786775f, + 1.1722589693f,1.1728391520f,1.1734192257f,1.1739991904f,1.1745790465f,1.1751587939f, + 1.1757384329f,1.1763179636f,1.1768973862f,1.1774767008f,1.1780559076f,1.1786350067f, + 1.1792139983f,1.1797928825f,1.1803716594f,1.1809503293f,1.1815288922f,1.1821073484f, + 1.1826856979f,1.1832639409f,1.1838420776f,1.1844201081f,1.1849980325f,1.1855758511f, + 1.1861535638f,1.1867311710f,1.1873086727f,1.1878860690f,1.1884633602f,1.1890405464f, + 1.1896176276f,1.1901946041f,1.1907714761f,1.1913482435f,1.1919249066f,1.1925014656f, + 1.1930779205f,1.1936542715f,1.1942305188f,1.1948066625f,1.1953827027f,1.1959586396f, + 1.1965344733f,1.1971102039f,1.1976858317f,1.1982613567f,1.1988367790f,1.1994120989f, + 1.1999873164f,1.2005624317f,1.2011374449f,1.2017123562f,1.2022871657f,1.2028618735f, + 1.2034364798f,1.2040109847f,1.2045853884f,1.2051596910f,1.2057338925f,1.2063079933f, + 1.2068819933f,1.2074558927f,1.2080296917f,1.2086033904f,1.2091769890f,1.2097504875f, + 1.2103238861f,1.2108971849f,1.2114703841f,1.2120434838f,1.2126164841f,1.2131893851f, + 1.2137621871f,1.2143348901f,1.2149074942f,1.2154799996f,1.2160524065f,1.2166247149f, + 1.2171969249f,1.2177690368f,1.2183410506f,1.2189129665f,1.2194847846f,1.2200565050f, + 1.2206281278f,1.2211996533f,1.2217710814f,1.2223424124f,1.2229136464f,1.2234847834f, + 1.2240558237f,1.2246267673f,1.2251976144f,1.2257683651f,1.2263390195f,1.2269095777f, + 1.2274800400f,1.2280504063f,1.2286206769f,1.2291908518f,1.2297609312f,1.2303309153f, + 1.2309008040f,1.2314705976f,1.2320402961f,1.2326098998f,1.2331794087f,1.2337488229f, + 1.2343181426f,1.2348873679f,1.2354564989f,1.2360255357f,1.2365944784f,1.2371633273f, + 1.2377320823f,1.2383007437f,1.2388693115f,1.2394377859f,1.2400061669f,1.2405744547f, + 1.2411426495f,1.2417107513f,1.2422787602f,1.2428466764f,1.2434145001f,1.2439822312f, + 1.2445498700f,1.2451174165f,1.2456848709f,1.2462522332f,1.2468195037f,1.2473866824f, + 1.2479537694f,1.2485207649f,1.2490876689f,1.2496544817f,1.2502212032f,1.2507878337f, + 1.2513543732f,1.2519208218f,1.2524871797f,1.2530534470f,1.2536196238f,1.2541857101f, + 1.2547517062f,1.2553176122f,1.2558834281f,1.2564491540f,1.2570147902f,1.2575803366f, + 1.2581457934f,1.2587111607f,1.2592764387f,1.2598416274f,1.2604067270f,1.2609717375f, + 1.2615366591f,1.2621014919f,1.2626662360f,1.2632308915f,1.2637954585f,1.2643599371f, + 1.2649243275f,1.2654886297f,1.2660528439f,1.2666169702f,1.2671810087f,1.2677449594f, + 1.2683088225f,1.2688725982f,1.2694362864f,1.2699998874f,1.2705634012f,1.2711268280f, + 1.2716901678f,1.2722534207f,1.2728165870f,1.2733796666f,1.2739426596f,1.2745055663f, + 1.2750683866f,1.2756311208f,1.2761937688f,1.2767563309f,1.2773188070f,1.2778811974f, + 1.2784435022f,1.2790057213f,1.2795678550f,1.2801299033f,1.2806918664f,1.2812537443f, + 1.2818155372f,1.2823772451f,1.2829388682f,1.2835004066f,1.2840618603f,1.2846232295f, + 1.2851845143f,1.2857457148f,1.2863068310f,1.2868678631f,1.2874288113f,1.2879896755f, + 1.2885504559f,1.2891111525f,1.2896717656f,1.2902322952f,1.2907927413f,1.2913531042f, + 1.2919133839f,1.2924735804f,1.2930336940f,1.2935937246f,1.2941536725f,1.2947135376f, + 1.2952733202f,1.2958330202f,1.2963926379f,1.2969521732f,1.2975116264f,1.2980709974f, + 1.2986302864f,1.2991894935f,1.2997486188f,1.3003076624f,1.3008666244f,1.3014255049f, + 1.3019843039f,1.3025430216f,1.3031016581f,1.3036602135f,1.3042186878f,1.3047770812f, + 1.3053353937f,1.3058936255f,1.3064517767f,1.3070098473f,1.3075678375f,1.3081257473f, + 1.3086835768f,1.3092413262f,1.3097989955f,1.3103565848f,1.3109140942f,1.3114715239f, + 1.3120288738f,1.3125861442f,1.3131433350f,1.3137004464f,1.3142574786f,1.3148144314f, + 1.3153713052f,1.3159280999f,1.3164848157f,1.3170414526f,1.3175980107f,1.3181544902f, + 1.3187108911f,1.3192672135f,1.3198234576f,1.3203796233f,1.3209357108f,1.3214917202f, + 1.3220476516f,1.3226035050f,1.3231592806f,1.3237149785f,1.3242705987f,1.3248261413f, + 1.3253816064f,1.3259369942f,1.3264923046f,1.3270475378f,1.3276026939f,1.3281577730f, + 1.3287127752f,1.3292677005f,1.3298225490f,1.3303773208f,1.3309320161f,1.3314866349f, + 1.3320411772f,1.3325956433f,1.3331500331f,1.3337043467f,1.3342585843f,1.3348127460f, + 1.3353668317f,1.3359208417f,1.3364747760f,1.3370286346f,1.3375824177f,1.3381361254f, + 1.3386897577f,1.3392433147f,1.3397967965f,1.3403502033f,1.3409035350f,1.3414567918f, + 1.3420099738f,1.3425630810f,1.3431161135f,1.3436690715f,1.3442219549f,1.3447747640f, + 1.3453274987f,1.3458801591f,1.3464327454f,1.3469852576f,1.3475376959f,1.3480900602f, + 1.3486423507f,1.3491945674f,1.3497467105f,1.3502987800f,1.3508507761f,1.3514026987f, + 1.3519545480f,1.3525063240f,1.3530580269f,1.3536096568f,1.3541612136f,1.3547126975f, + 1.3552641086f,1.3558154470f,1.3563667127f,1.3569179058f,1.3574690264f,1.3580200745f, + 1.3585710504f,1.3591219540f,1.3596727854f,1.3602235447f,1.3607742320f,1.3613248474f, + 1.3618753909f,1.3624258627f,1.3629762627f,1.3635265912f,1.3640768481f,1.3646270336f, + 1.3651771477f,1.3657271905f,1.3662771621f,1.3668270625f,1.3673768919f,1.3679266504f, + 1.3684763379f,1.3690259546f,1.3695755006f,1.3701249759f,1.3706743807f,1.3712237149f, + 1.3717729787f,1.3723221722f,1.3728712954f,1.3734203484f,1.3739693313f,1.3745182442f, + 1.3750670871f,1.3756158601f,1.3761645634f,1.3767131969f,1.3772617607f,1.3778102550f, + 1.3783586797f,1.3789070351f,1.3794553211f,1.3800035379f,1.3805516854f,1.3810997639f, + 1.3816477733f,1.3821957137f,1.3827435853f,1.3832913880f,1.3838391220f,1.3843867873f, + 1.3849343841f,1.3854819123f,1.3860293721f,1.3865767636f,1.3871240867f,1.3876713417f, + 1.3882185285f,1.3887656472f,1.3893126980f,1.3898596808f,1.3904065958f,1.3909534430f, + 1.3915002225f,1.3920469345f,1.3925935788f,1.3931401557f,1.3936866652f,1.3942331073f, + 1.3947794822f,1.3953257900f,1.3958720306f,1.3964182041f,1.3969643107f,1.3975103504f, + 1.3980563233f,1.3986022295f,1.3991480689f,1.3996938418f,1.4002395481f,1.4007851880f, + 1.4013307615f,1.4018762686f,1.4024217095f,1.4029670843f,1.4035123929f,1.4040576355f, + 1.4046028121f,1.4051479229f,1.4056929678f,1.4062379470f,1.4067828605f,1.4073277083f, + 1.4078724907f,1.4084172075f,1.4089618590f,1.4095064452f,1.4100509660f,1.4105954217f, + 1.4111398123f,1.4116841378f,1.4122283983f,1.4127725939f,1.4133167247f,1.4138607907f, + 1.4144047919f,1.4149487286f,1.4154926007f,1.4160364082f,1.4165801514f,1.4171238301f, + 1.4176674446f,1.4182109949f,1.4187544809f,1.4192979029f,1.4198412609f,1.4203845549f, + 1.4209277850f,1.4214709513f,1.4220140539f,1.4225570927f,1.4231000679f,1.4236429796f, + 1.4241858278f,1.4247286125f,1.4252713340f,1.4258139921f,1.4263565870f,1.4268991187f, + 1.4274415874f,1.4279839930f,1.4285263357f,1.4290686155f,1.4296108324f,1.4301529866f, + 1.4306950781f,1.4312371070f,1.4317790733f,1.4323209771f,1.4328628185f,1.4334045976f, + 1.4339463143f,1.4344879687f,1.4350295611f,1.4355710913f,1.4361125594f,1.4366539656f, + 1.4371953098f,1.4377365923f,1.4382778129f,1.4388189718f,1.4393600690f,1.4399011047f, + 1.4404420788f,1.4409829915f,1.4415238427f,1.4420646326f,1.4426053613f,1.4431460287f, + 1.4436866350f,1.4442271801f,1.4447676643f,1.4453080875f,1.4458484498f,1.4463887513f, + 1.4469289920f,1.4474691720f,1.4480092913f,1.4485493501f,1.4490893483f,1.4496292861f, + 1.4501691634f,1.4507089805f,1.4512487372f,1.4517884338f,1.4523280702f,1.4528676465f, + 1.4534071627f,1.4539466191f,1.4544860155f,1.4550253520f,1.4555646288f,1.4561038458f, + 1.4566430032f,1.4571821010f,1.4577211393f,1.4582601180f,1.4587990374f,1.4593378974f, + 1.4598766981f,1.4604154395f,1.4609541218f,1.4614927449f,1.4620313090f,1.4625698141f, + 1.4631082603f,1.4636466476f,1.4641849761f,1.4647232458f,1.4652614568f,1.4657996091f, + 1.4663377029f,1.4668757382f,1.4674137150f,1.4679516334f,1.4684894934f,1.4690272952f, + 1.4695650387f,1.4701027241f,1.4706403514f,1.4711779206f,1.4717154318f,1.4722528850f, + 1.4727902804f,1.4733276180f,1.4738648978f,1.4744021200f,1.4749392844f,1.4754763913f, + 1.4760134407f,1.4765504325f,1.4770873670f,1.4776242441f,1.4781610639f,1.4786978265f, + 1.4792345319f,1.4797711801f,1.4803077713f,1.4808443055f,1.4813807827f,1.4819172030f, + 1.4824535665f,1.4829898731f,1.4835261231f,1.4840623164f,1.4845984530f,1.4851345331f, + 1.4856705567f,1.4862065238f,1.4867424346f,1.4872782890f,1.4878140871f,1.4883498290f, + 1.4888855147f,1.4894211443f,1.4899567179f,1.4904922354f,1.4910276970f,1.4915631027f, + 1.4920984526f,1.4926337467f,1.4931689850f,1.4937041677f,1.4942392947f,1.4947743662f, + 1.4953093822f,1.4958443427f,1.4963792479f,1.4969140977f,1.4974488922f,1.4979836314f, + 1.4985183155f,1.4990529444f,1.4995875183f,1.5001220371f,1.5006565010f,1.5011909100f, + 1.5017252641f,1.5022595634f,1.5027938080f,1.5033279978f,1.5038621330f,1.5043962137f, + 1.5049302397f,1.5054642113f,1.5059981285f,1.5065319913f,1.5070657997f,1.5075995539f, + 1.5081332539f,1.5086668997f,1.5092004913f,1.5097340290f,1.5102675126f,1.5108009422f, + 1.5113343180f,1.5118676399f,1.5124009080f,1.5129341223f,1.5134672829f,1.5140003899f, + 1.5145334434f,1.5150664432f,1.5155993896f,1.5161322825f,1.5166651221f,1.5171979083f, + 1.5177306413f,1.5182633210f,1.5187959475f,1.5193285209f,1.5198610412f,1.5203935085f, + 1.5209259228f,1.5214582842f,1.5219905927f,1.5225228484f,1.5230550514f,1.5235872016f, + 1.5241192991f,1.5246513440f,1.5251833364f,1.5257152762f,1.5262471635f,1.5267789985f, + 1.5273107810f,1.5278425113f,1.5283741893f,1.5289058150f,1.5294373886f,1.5299689101f, + 1.5305003795f,1.5310317969f,1.5315631623f,1.5320944758f,1.5326257374f,1.5331569472f, + 1.5336881052f,1.5342192115f,1.5347502662f,1.5352812692f,1.5358122206f,1.5363431205f, + 1.5368739689f,1.5374047659f,1.5379355115f,1.5384662058f,1.5389968488f,1.5395274406f, + 1.5400579811f,1.5405884706f,1.5411189089f,1.5416492962f,1.5421796326f,1.5427099179f, + 1.5432401524f,1.5437703360f,1.5443004689f,1.5448305509f,1.5453605823f,1.5458905630f, + 1.5464204931f,1.5469503726f,1.5474802017f,1.5480099802f,1.5485397084f,1.5490693861f, + 1.5495990136f,1.5501285907f,1.5506581177f,1.5511875944f,1.5517170210f,1.5522463976f, + 1.5527757240f,1.5533050005f,1.5538342271f,1.5543634037f,1.5548925305f,1.5554216075f, + 1.5559506347f,1.5564796122f,1.5570085400f,1.5575374182f,1.5580662468f,1.5585950259f, + 1.5591237555f,1.5596524357f,1.5601810664f,1.5607096479f,1.5612381800f,1.5617666629f, + 1.5622950965f,1.5628234810f,1.5633518164f,1.5638801027f,1.5644083400f,1.5649365283f, + 1.5654646677f,1.5659927582f,1.5665207998f,1.5670487926f,1.5675767367f,1.5681046321f, + 1.5686324788f,1.5691602769f,1.5696880264f,1.5702157273f,1.5707433798f,1.5712709839f, + 1.5717985395f,1.5723260468f,1.5728535058f,1.5733809166f,1.5739082791f,1.5744355934f, + 1.5749628596f,1.5754900777f,1.5760172478f,1.5765443699f,1.5770714440f,1.5775984703f, + 1.5781254486f,1.5786523791f,1.5791792619f,1.5797060969f,1.5802328843f,1.5807596240f, + 1.5812863160f,1.5818129606f,1.5823395576f,1.5828661071f,1.5833926092f,1.5839190639f, + 1.5844454713f,1.5849718314f,1.5854981442f,1.5860244098f,1.5865506283f,1.5870767996f, + 1.5876029238f,1.5881290010f,1.5886550312f,1.5891810144f,1.5897069507f,1.5902328402f, + 1.5907586828f,1.5912844786f,1.5918102277f,1.5923359301f,1.5928615858f,1.5933871949f, + 1.5939127575f,1.5944382735f,1.5949637430f,1.5954891661f,1.5960145427f,1.5965398730f, + 1.5970651570f,1.5975903947f,1.5981155861f,1.5986407314f,1.5991658305f,1.5996908835f, + 1.6002158904f,1.6007408513f,1.6012657662f,1.6017906351f,1.6023154581f,1.6028402353f, + 1.6033649667f,1.6038896522f,1.6044142920f,1.6049388862f,1.6054634346f,1.6059879375f, + 1.6065123947f,1.6070368065f,1.6075611727f,1.6080854935f,1.6086097688f,1.6091339988f, + 1.6096581834f,1.6101823228f,1.6107064169f,1.6112304658f,1.6117544695f,1.6122784281f, + 1.6128023416f,1.6133262100f,1.6138500335f,1.6143738119f,1.6148975454f,1.6154212341f, + 1.6159448779f,1.6164684768f,1.6169920310f,1.6175155405f,1.6180390053f,1.6185624254f, + 1.6190858009f,1.6196091319f,1.6201324183f,1.6206556602f,1.6211788576f,1.6217020107f, + 1.6222251193f,1.6227481836f,1.6232712037f,1.6237941794f,1.6243171110f,1.6248399984f, + 1.6253628416f,1.6258856407f,1.6264083958f,1.6269311068f,1.6274537738f,1.6279763969f, + 1.6284989761f,1.6290215115f,1.6295440030f,1.6300664507f,1.6305888546f,1.6311112148f, + 1.6316335314f,1.6321558043f,1.6326780336f,1.6332002194f,1.6337223616f,1.6342444603f, + 1.6347665156f,1.6352885275f,1.6358104960f,1.6363324212f,1.6368543031f,1.6373761417f, + 1.6378979371f,1.6384196893f,1.6389413984f,1.6394630644f,1.6399846873f,1.6405062672f, + 1.6410278040f,1.6415492980f,1.6420707490f,1.6425921571f,1.6431135223f,1.6436348448f, + 1.6441561245f,1.6446773615f,1.6451985557f,1.6457197074f,1.6462408163f,1.6467618827f, + 1.6472829066f,1.6478038880f,1.6483248268f,1.6488457233f,1.6493665773f,1.6498873890f, + 1.6504081583f,1.6509288854f,1.6514495702f,1.6519702128f,1.6524908132f,1.6530113715f, + 1.6535318876f,1.6540523617f,1.6545727938f,1.6550931838f,1.6556135319f,1.6561338381f, + 1.6566541023f,1.6571743248f,1.6576945054f,1.6582146442f,1.6587347413f,1.6592547967f, + 1.6597748104f,1.6602947824f,1.6608147129f,1.6613346018f,1.6618544491f,1.6623742550f, + 1.6628940194f,1.6634137424f,1.6639334240f,1.6644530642f,1.6649726631f,1.6654922208f, + 1.6660117372f,1.6665312124f,1.6670506464f,1.6675700392f,1.6680893910f,1.6686087017f, + 1.6691279714f,1.6696472000f,1.6701663877f,1.6706855345f,1.6712046403f,1.6717237053f, + 1.6722427295f,1.6727617129f,1.6732806555f,1.6737995575f,1.6743184187f,1.6748372393f, + 1.6753560192f,1.6758747586f,1.6763934574f,1.6769121157f,1.6774307335f,1.6779493109f, + 1.6784678479f,1.6789863445f,1.6795048007f,1.6800232167f,1.6805415924f,1.6810599278f, + 1.6815782230f,1.6820964781f,1.6826146930f,1.6831328678f,1.6836510025f,1.6841690972f, + 1.6846871519f,1.6852051667f,1.6857231415f,1.6862410764f,1.6867589714f,1.6872768266f, + 1.6877946420f,1.6883124176f,1.6888301535f,1.6893478497f,1.6898655062f,1.6903831231f, + 1.6909007004f,1.6914182381f,1.6919357363f,1.6924531950f,1.6929706142f,1.6934879940f, + 1.6940053344f,1.6945226354f,1.6950398971f,1.6955571195f,1.6960743026f,1.6965914465f, + 1.6971085512f,1.6976256167f,1.6981426430f,1.6986596303f,1.6991765785f,1.6996934877f, + 1.7002103578f,1.7007271890f,1.7012439812f,1.7017607345f,1.7022774490f,1.7027941246f, + 1.7033107613f,1.7038273594f,1.7043439186f,1.7048604392f,1.7053769210f,1.7058933642f, + 1.7064097688f,1.7069261348f,1.7074424623f,1.7079587512f,1.7084750016f,1.7089912136f, + 1.7095073872f,1.7100235223f,1.7105396191f,1.7110556776f,1.7115716977f,1.7120876796f, + 1.7126036233f,1.7131195288f,1.7136353960f,1.7141512252f,1.7146670162f,1.7151827692f, + 1.7156984841f,1.7162141610f,1.7167297999f,1.7172454008f,1.7177609639f,1.7182764890f, + 1.7187919763f,1.7193074258f,1.7198228374f,1.7203382113f,1.7208535475f,1.7213688459f, + 1.7218841067f,1.7223993299f,1.7229145154f,1.7234296634f,1.7239447738f,1.7244598467f, + 1.7249748821f,1.7254898800f,1.7260048405f,1.7265197637f,1.7270346494f,1.7275494979f, + 1.7280643090f,1.7285790828f,1.7290938195f,1.7296085189f,1.7301231811f,1.7306378062f, + 1.7311523941f,1.7316669450f,1.7321814588f,1.7326959356f,1.7332103753f,1.7337247781f, + 1.7342391440f,1.7347534730f,1.7352677650f,1.7357820203f,1.7362962387f,1.7368104203f, + 1.7373245652f,1.7378386733f,1.7383527448f,1.7388667795f,1.7393807777f,1.7398947392f, + 1.7404086641f,1.7409225525f,1.7414364044f,1.7419502198f,1.7424639987f,1.7429777412f, + 1.7434914473f,1.7440051170f,1.7445187504f,1.7450323475f,1.7455459083f,1.7460594328f, + 1.7465729211f,1.7470863732f,1.7475997891f,1.7481131689f,1.7486265126f,1.7491398202f, + 1.7496530918f,1.7501663273f,1.7506795269f,1.7511926904f,1.7517058181f,1.7522189098f, + 1.7527319657f,1.7532449857f,1.7537579699f,1.7542709184f,1.7547838310f,1.7552967079f, + 1.7558095492f,1.7563223547f,1.7568351246f,1.7573478589f,1.7578605576f,1.7583732207f, + 1.7588858483f,1.7593984404f,1.7599109970f,1.7604235182f,1.7609360040f,1.7614484543f, + 1.7619608693f,1.7624732490f,1.7629855934f,1.7634979025f,1.7640101763f,1.7645224150f, + 1.7650346184f,1.7655467867f,1.7660589198f,1.7665710178f,1.7670830808f,1.7675951087f, + 1.7681071015f,1.7686190594f,1.7691309823f,1.7696428703f,1.7701547234f,1.7706665415f, + 1.7711783248f,1.7716900733f,1.7722017870f,1.7727134659f,1.7732251101f,1.7737367195f, + 1.7742482943f,1.7747598344f,1.7752713399f,1.7757828107f,1.7762942470f,1.7768056487f, + 1.7773170159f,1.7778283486f,1.7783396468f,1.7788509106f,1.7793621399f,1.7798733349f, + 1.7803844955f,1.7808956218f,1.7814067137f,1.7819177714f,1.7824287948f,1.7829397840f, + 1.7834507390f,1.7839616599f,1.7844725465f,1.7849833991f,1.7854942176f,1.7860050020f, + 1.7865157524f,1.7870264687f,1.7875371511f,1.7880477995f,1.7885584140f,1.7890689946f, + 1.7895795412f,1.7900900541f,1.7906005331f,1.7911109783f,1.7916213898f,1.7921317675f, + 1.7926421115f,1.7931524218f,1.7936626984f,1.7941729414f,1.7946831508f,1.7951933266f, + 1.7957034688f,1.7962135775f,1.7967236527f,1.7972336944f,1.7977437026f,1.7982536775f, + 1.7987636189f,1.7992735269f,1.7997834016f,1.8002932430f,1.8008030510f,1.8013128258f, + 1.8018225674f,1.8023322757f,1.8028419508f,1.8033515928f,1.8038612016f,1.8043707773f, + 1.8048803199f,1.8053898294f,1.8058993059f,1.8064087494f,1.8069181599f,1.8074275374f, + 1.8079368820f,1.8084461937f,1.8089554725f,1.8094647184f,1.8099739315f,1.8104831118f, + 1.8109922593f,1.8115013740f,1.8120104561f,1.8125195054f,1.8130285220f,1.8135375059f, + 1.8140464572f,1.8145553760f,1.8150642621f,1.8155731157f,1.8160819367f,1.8165907253f, + 1.8170994813f,1.8176082049f,1.8181168961f,1.8186255548f,1.8191341812f,1.8196427752f, + 1.8201513369f,1.8206598663f,1.8211683634f,1.8216768283f,1.8221852609f,1.8226936613f, + 1.8232020295f,1.8237103656f,1.8242186695f,1.8247269413f,1.8252351811f,1.8257433888f, + 1.8262515644f,1.8267597080f,1.8272678197f,1.8277758994f,1.8282839471f,1.8287919630f, + 1.8292999469f,1.8298078990f,1.8303158193f,1.8308237077f,1.8313315644f,1.8318393893f, + 1.8323471824f,1.8328549439f,1.8333626736f,1.8338703717f,1.8343780381f,1.8348856729f, + 1.8353932761f,1.8359008477f,1.8364083878f,1.8369158964f,1.8374233735f,1.8379308191f, + 1.8384382332f,1.8389456159f,1.8394529672f,1.8399602872f,1.8404675757f,1.8409748330f, + 1.8414820589f,1.8419892536f,1.8424964170f,1.8430035491f,1.8435106501f,1.8440177198f, + 1.8445247584f,1.8450317658f,1.8455387422f,1.8460456874f,1.8465526016f,1.8470594847f, + 1.8475663368f,1.8480731579f,1.8485799480f,1.8490867072f,1.8495934354f,1.8501001327f, + 1.8506067992f,1.8511134347f,1.8516200395f,1.8521266134f,1.8526331566f,1.8531396690f, + 1.8536461506f,1.8541526015f,1.8546590217f,1.8551654113f,1.8556717702f,1.8561780984f, + 1.8566843961f,1.8571906632f,1.8576968997f,1.8582031057f,1.8587092812f,1.8592154262f, + 1.8597215407f,1.8602276248f,1.8607336784f,1.8612397017f,1.8617456946f,1.8622516571f, + 1.8627575893f,1.8632634912f,1.8637693628f,1.8642752042f,1.8647810153f,1.8652867962f, + 1.8657925469f,1.8662982674f,1.8668039578f,1.8673096181f,1.8678152482f,1.8683208483f, + 1.8688264184f,1.8693319583f,1.8698374683f,1.8703429483f,1.8708483983f,1.8713538184f, + 1.8718592086f,1.8723645688f,1.8728698992f,1.8733751997f,1.8738804704f,1.8743857113f, + 1.8748909224f,1.8753961037f,1.8759012553f,1.8764063771f,1.8769114693f,1.8774165318f, + 1.8779215646f,1.8784265678f,1.8789315413f,1.8794364853f,1.8799413997f,1.8804462846f, + 1.8809511399f,1.8814559658f,1.8819607622f,1.8824655291f,1.8829702665f,1.8834749746f, + 1.8839796533f,1.8844843025f,1.8849889225f,1.8854935131f,1.8859980744f,1.8865026064f, + 1.8870071092f,1.8875115827f,1.8880160270f,1.8885204422f,1.8890248281f,1.8895291849f, + 1.8900335125f,1.8905378111f,1.8910420805f,1.8915463209f,1.8920505322f,1.8925547145f, + 1.8930588678f,1.8935629921f,1.8940670875f,1.8945711539f,1.8950751914f,1.8955792000f, + 1.8960831798f,1.8965871306f,1.8970910527f,1.8975949459f,1.8980988104f,1.8986026460f, + 1.8991064530f,1.8996102312f,1.9001139807f,1.9006177015f,1.9011213936f,1.9016250571f, + 1.9021286920f,1.9026322983f,1.9031358760f,1.9036394251f,1.9041429457f,1.9046464378f, + 1.9051499014f,1.9056533365f,1.9061567432f,1.9066601214f,1.9071634712f,1.9076667926f, + 1.9081700857f,1.9086733504f,1.9091765867f,1.9096797948f,1.9101829746f,1.9106861261f, + 1.9111892494f,1.9116923444f,1.9121954112f,1.9126984499f,1.9132014603f,1.9137044427f, + 1.9142073969f,1.9147103230f,1.9152132210f,1.9157160910f,1.9162189329f,1.9167217468f, + 1.9172245327f,1.9177272907f,1.9182300206f,1.9187327226f,1.9192353968f,1.9197380430f, + 1.9202406613f,1.9207432518f,1.9212458144f,1.9217483492f,1.9222508562f,1.9227533355f, + 1.9232557869f,1.9237582107f,1.9242606067f,1.9247629750f,1.9252653157f,1.9257676287f, + 1.9262699140f,1.9267721718f,1.9272744019f,1.9277766045f,1.9282787795f,1.9287809270f, + 1.9292830469f,1.9297851394f,1.9302872044f,1.9307892419f,1.9312912520f,1.9317932347f, + 1.9322951899f,1.9327971178f,1.9332990184f,1.9338008916f,1.9343027374f,1.9348045560f, + 1.9353063473f,1.9358081114f,1.9363098482f,1.9368115577f,1.9373132401f,1.9378148953f, + 1.9383165233f,1.9388181242f,1.9393196980f,1.9398212447f,1.9403227642f,1.9408242568f, + 1.9413257222f,1.9418271607f,1.9423285721f,1.9428299566f,1.9433313141f,1.9438326446f, + 1.9443339482f,1.9448352249f,1.9453364747f,1.9458376976f,1.9463388937f,1.9468400630f, + 1.9473412054f,1.9478423211f,1.9483434099f,1.9488444720f,1.9493455074f,1.9498465161f, + 1.9503474981f,1.9508484533f,1.9513493820f,1.9518502839f,1.9523511593f,1.9528520081f, + 1.9533528302f,1.9538536258f,1.9543543949f,1.9548551374f,1.9553558535f,1.9558565430f, + 1.9563572061f,1.9568578427f,1.9573584529f,1.9578590366f,1.9583595940f,1.9588601250f, + 1.9593606296f,1.9598611079f,1.9603615599f,1.9608619856f,1.9613623850f,1.9618627581f, + 1.9623631050f,1.9628634257f,1.9633637201f,1.9638639884f,1.9643642305f,1.9648644464f, + 1.9653646362f,1.9658647999f,1.9663649375f,1.9668650490f,1.9673651344f,1.9678651939f, + 1.9683652272f,1.9688652346f,1.9693652160f,1.9698651715f,1.9703651009f,1.9708650045f, + 1.9713648821f,1.9718647339f,1.9723645597f,1.9728643597f,1.9733641339f,1.9738638822f, + 1.9743636048f,1.9748633015f,1.9753629725f,1.9758626178f,1.9763622373f,1.9768618311f, + 1.9773613992f,1.9778609416f,1.9783604584f,1.9788599495f,1.9793594150f,1.9798588549f, + 1.9803582693f,1.9808576580f,1.9813570212f,1.9818563589f,1.9823556711f,1.9828549577f, + 1.9833542189f,1.9838534546f,1.9843526649f,1.9848518498f,1.9853510092f,1.9858501433f, + 1.9863492520f,1.9868483353f,1.9873473933f,1.9878464260f,1.9883454334f,1.9888444155f, + 1.9893433723f,1.9898423039f,1.9903412102f,1.9908400914f,1.9913389474f,1.9918377781f, + 1.9923365838f,1.9928353642f,1.9933341196f,1.9938328498f,1.9943315550f,1.9948302351f, + 1.9953288901f,1.9958275201f,1.9963261251f,1.9968247051f,1.9973232601f,1.9978217901f, + 1.9983202952f,1.9988187754f,1.9993172306f,1.9998156610f,2.0003140664f,2.0008124470f, + 2.0013108028f,2.0018091337f,2.0023074398f,2.0028057212f,2.0033039777f,2.0038022095f, + 2.0043004166f,2.0047985989f,2.0052967565f,2.0057948894f,2.0062929977f,2.0067910813f, + 2.0072891403f,2.0077871746f,2.0082851844f,2.0087831695f,2.0092811301f,2.0097790661f, + 2.0102769776f,2.0107748646f,2.0112727271f,2.0117705651f,2.0122683786f,2.0127661677f, + 2.0132639323f,2.0137616725f,2.0142593884f,2.0147570798f,2.0152547469f,2.0157523896f, + 2.0162500080f,2.0167476020f,2.0172451718f,2.0177427173f,2.0182402385f,2.0187377355f, + 2.0192352082f,2.0197326568f,2.0202300811f,2.0207274812f,2.0212248572f,2.0217222091f, + 2.0222195368f,2.0227168403f,2.0232141198f,2.0237113752f,2.0242086066f,2.0247058138f, + 2.0252029971f,2.0257001563f,2.0261972916f,2.0266944028f,2.0271914901f,2.0276885535f, + 2.0281855929f,2.0286826083f,2.0291795999f,2.0296765676f,2.0301735115f,2.0306704314f, + 2.0311673276f,2.0316641999f,2.0321610484f,2.0326578732f,2.0331546741f,2.0336514514f, + 2.0341482048f,2.0346449346f,2.0351416406f,2.0356383230f,2.0361349817f,2.0366316167f, + 2.0371282281f,2.0376248159f,2.0381213800f,2.0386179206f,2.0391144376f,2.0396109310f, + 2.0401074009f,2.0406038472f,2.0411002701f,2.0415966694f,2.0420930453f,2.0425893977f, + 2.0430857267f,2.0435820322f,2.0440783143f,2.0445745730f,2.0450708084f,2.0455670203f, + 2.0460632089f,2.0465593742f,2.0470555162f,2.0475516348f,2.0480477302f,2.0485438023f, + 2.0490398511f,2.0495358767f,2.0500318791f,2.0505278582f,2.0510238142f,2.0515197470f, + 2.0520156566f,2.0525115431f,2.0530074064f,2.0535032467f,2.0539990638f,2.0544948579f, + 2.0549906288f,2.0554863768f,2.0559821017f,2.0564778035f,2.0569734824f,2.0574691383f, + 2.0579647712f,2.0584603811f,2.0589559681f,2.0594515322f,2.0599470734f,2.0604425916f, + 2.0609380870f,2.0614335595f,2.0619290092f,2.0624244360f,2.0629198400f,2.0634152213f, + 2.0639105797f,2.0644059153f,2.0649012282f,2.0653965184f,2.0658917858f,2.0663870305f, + 2.0668822525f,2.0673774518f,2.0678726285f,2.0683677825f,2.0688629139f,2.0693580226f, + 2.0698531088f,2.0703481723f,2.0708432133f,2.0713382318f,2.0718332276f,2.0723282010f, + 2.0728231518f,2.0733180802f,2.0738129860f,2.0743078694f,2.0748027304f,2.0752975689f, + 2.0757923849f,2.0762871786f,2.0767819499f,2.0772766988f,2.0777714253f,2.0782661295f, + 2.0787608113f,2.0792554709f,2.0797501081f,2.0802447230f,2.0807393157f,2.0812338861f, + 2.0817284343f,2.0822229602f,2.0827174639f,2.0832119454f,2.0837064048f,2.0842008419f, + 2.0846952570f,2.0851896498f,2.0856840206f,2.0861783692f,2.0866726958f,2.0871670002f, + 2.0876612826f,2.0881555430f,2.0886497813f,2.0891439976f,2.0896381918f,2.0901323641f, + 2.0906265144f,2.0911206428f,2.0916147492f,2.0921088337f,2.0926028962f,2.0930969368f, + 2.0935909556f,2.0940849525f,2.0945789275f,2.0950728807f,2.0955668120f,2.0960607215f, + 2.0965546092f,2.0970484751f,2.0975423193f,2.0980361417f,2.0985299423f,2.0990237213f, + 2.0995174785f,2.1000112140f,2.1005049278f,2.1009986199f,2.1014922904f,2.1019859392f, + 2.1024795665f,2.1029731720f,2.1034667560f,2.1039603184f,2.1044538593f,2.1049473786f, + 2.1054408763f,2.1059343525f,2.1064278072f,2.1069212404f,2.1074146521f,2.1079080423f, + 2.1084014110f,2.1088947584f,2.1093880843f,2.1098813887f,2.1103746718f,2.1108679335f, + 2.1113611738f,2.1118543928f,2.1123475904f,2.1128407666f,2.1133339216f,2.1138270553f, + 2.1143201676f,2.1148132587f,2.1153063286f,2.1157993771f,2.1162924045f,2.1167854106f, + 2.1172783956f,2.1177713593f,2.1182643018f,2.1187572232f,2.1192501235f,2.1197430026f, + 2.1202358606f,2.1207286975f,2.1212215133f,2.1217143080f,2.1222070816f,2.1226998342f, + 2.1231925658f,2.1236852763f,2.1241779658f,2.1246706343f,2.1251632819f,2.1256559084f, + 2.1261485141f,2.1266410987f,2.1271336625f,2.1276262053f,2.1281187272f,2.1286112283f, + 2.1291037084f,2.1295961677f,2.1300886062f,2.1305810238f,2.1310734206f,2.1315657967f, + 2.1320581519f,2.1325504863f,2.1330428000f,2.1335350929f,2.1340273651f,2.1345196166f, + 2.1350118473f,2.1355040574f,2.1359962467f,2.1364884154f,2.1369805635f,2.1374726909f, + 2.1379647977f,2.1384568838f,2.1389489494f,2.1394409944f,2.1399330188f,2.1404250226f, + 2.1409170059f,2.1414089686f,2.1419009109f,2.1423928326f,2.1428847338f,2.1433766146f, + 2.1438684748f,2.1443603147f,2.1448521341f,2.1453439330f,2.1458357115f,2.1463274697f, + 2.1468192074f,2.1473109248f,2.1478026218f,2.1482942985f,2.1487859548f,2.1492775908f, + 2.1497692065f,2.1502608019f,2.1507523770f,2.1512439319f,2.1517354665f,2.1522269808f, + 2.1527184749f,2.1532099488f,2.1537014025f,2.1541928361f,2.1546842494f,2.1551756426f, + 2.1556670156f,2.1561583684f,2.1566497012f,2.1571410138f,2.1576323064f,2.1581235788f, + 2.1586148312f,2.1591060635f,2.1595972758f,2.1600884680f,2.1605796403f,2.1610707925f, + 2.1615619247f,2.1620530369f,2.1625441292f,2.1630352015f,2.1635262538f,2.1640172863f, + 2.1645082988f,2.1649992914f,2.1654902641f,2.1659812169f,2.1664721499f,2.1669630630f, + 2.1674539563f,2.1679448297f,2.1684356833f,2.1689265172f,2.1694173312f,2.1699081255f, + 2.1703989000f,2.1708896547f,2.1713803897f,2.1718711050f,2.1723618005f,2.1728524764f, + 2.1733431326f,2.1738337691f,2.1743243859f,2.1748149831f,2.1753055606f,2.1757961185f, + 2.1762866568f,2.1767771755f,2.1772676747f,2.1777581542f,2.1782486142f,2.1787390546f, + 2.1792294755f,2.1797198769f,2.1802102587f,2.1807006211f,2.1811909639f,2.1816812873f, + 2.1821715913f,2.1826618758f,2.1831521408f,2.1836423864f,2.1841326126f,2.1846228194f, + 2.1851130069f,2.1856031749f,2.1860933236f,2.1865834529f,2.1870735629f,2.1875636536f, + 2.1880537249f,2.1885437770f,2.1890338097f,2.1895238232f,2.1900138175f,2.1905037924f, + 2.1909937482f,2.1914836847f,2.1919736020f,2.1924635001f,2.1929533790f,2.1934432387f, + 2.1939330792f,2.1944229006f,2.1949127029f,2.1954024860f,2.1958922500f,2.1963819949f, + 2.1968717207f,2.1973614275f,2.1978511151f,2.1983407838f,2.1988304333f,2.1993200639f, + 2.1998096754f,2.2002992679f,2.2007888414f,2.2012783959f,2.2017679315f,2.2022574481f, + 2.2027469457f,2.2032364244f,2.2037258842f,2.2042153251f,2.2047047471f,2.2051941501f, + 2.2056835344f,2.2061728997f,2.2066622462f,2.2071515739f,2.2076408827f,2.2081301727f, + 2.2086194439f,2.2091086964f,2.2095979300f,2.2100871449f,2.2105763410f,2.2110655184f, + 2.2115546770f,2.2120438169f,2.2125329381f,2.2130220406f,2.2135111245f,2.2140001896f, + 2.2144892361f,2.2149782640f,2.2154672732f,2.2159562638f,2.2164452357f,2.2169341891f, + 2.2174231239f,2.2179120401f,2.2184009377f,2.2188898168f,2.2193786773f,2.2198675193f, + 2.2203563428f,2.2208451477f,2.2213339342f,2.2218227022f,2.2223114517f,2.2228001827f, + 2.2232888953f,2.2237775895f,2.2242662652f,2.2247549226f,2.2252435615f,2.2257321820f, + 2.2262207841f,2.2267093679f,2.2271979333f,2.2276864804f,2.2281750091f,2.2286635195f, + 2.2291520116f,2.2296404854f,2.2301289409f,2.2306173782f,2.2311057971f,2.2315941978f, + 2.2320825803f,2.2325709446f,2.2330592906f,2.2335476184f,2.2340359280f,2.2345242194f, + 2.2350124927f,2.2355007478f,2.2359889847f,2.2364772035f,2.2369654042f,2.2374535867f, + 2.2379417512f,2.2384298975f,2.2389180258f,2.2394061360f,2.2398942282f,2.2403823023f, + 2.2408703583f,2.2413583963f,2.2418464163f,2.2423344184f,2.2428224024f,2.2433103684f, + 2.2437983165f,2.2442862466f,2.2447741587f,2.2452620529f,2.2457499292f,2.2462377876f, + 2.2467256281f,2.2472134507f,2.2477012554f,2.2481890422f,2.2486768112f,2.2491645623f, + 2.2496522956f,2.2501400111f,2.2506277088f,2.2511153886f,2.2516030507f,2.2520906950f, + 2.2525783215f,2.2530659302f,2.2535535212f,2.2540410945f,2.2545286500f,2.2550161879f, + 2.2555037080f,2.2559912104f,2.2564786952f,2.2569661623f,2.2574536117f,2.2579410435f, + 2.2584284576f,2.2589158542f,2.2594032331f,2.2598905943f,2.2603779381f,2.2608652642f, + 2.2613525727f,2.2618398637f,2.2623271372f,2.2628143931f,2.2633016314f,2.2637888523f, + 2.2642760556f,2.2647632415f,2.2652504099f,2.2657375608f,2.2662246942f,2.2667118102f, + 2.2671989087f,2.2676859898f,2.2681730535f,2.2686600998f,2.2691471287f,2.2696341402f, + 2.2701211343f,2.2706081111f,2.2710950705f,2.2715820125f,2.2720689373f,2.2725558447f, + 2.2730427347f,2.2735296075f,2.2740164630f,2.2745033013f,2.2749901222f,2.2754769259f, + 2.2759637123f,2.2764504816f,2.2769372335f,2.2774239683f,2.2779106859f,2.2783973862f, + 2.2788840694f,2.2793707354f,2.2798573843f,2.2803440160f,2.2808306305f,2.2813172279f, + 2.2818038082f,2.2822903714f,2.2827769175f,2.2832634465f,2.2837499584f,2.2842364533f, + 2.2847229311f,2.2852093919f,2.2856958356f,2.2861822623f,2.2866686719f,2.2871550646f, + 2.2876414403f,2.2881277990f,2.2886141407f,2.2891004655f,2.2895867733f,2.2900730641f, + 2.2905593381f,2.2910455951f,2.2915318352f,2.2920180584f,2.2925042647f,2.2929904541f, + 2.2934766267f,2.2939627824f,2.2944489212f,2.2949350432f,2.2954211484f,2.2959072368f, + 2.2963933083f,2.2968793631f,2.2973654011f,2.2978514223f,2.2983374267f,2.2988234144f, + 2.2993093853f,2.2997953395f,2.3002812770f,2.3007671977f,2.3012531018f,2.3017389891f, + 2.3022248598f,2.3027107138f,2.3031965512f,2.3036823718f,2.3041681759f,2.3046539633f, + 2.3051397341f,2.3056254882f,2.3061112258f,2.3065969468f,2.3070826512f,2.3075683390f, + 2.3080540102f,2.3085396650f,2.3090253031f,2.3095109247f,2.3099965299f,2.3104821185f, + 2.3109676906f,2.3114532462f,2.3119387853f,2.3124243079f,2.3129098141f,2.3133953039f, + 2.3138807772f,2.3143662340f,2.3148516745f,2.3153370985f,2.3158225062f,2.3163078974f, + 2.3167932723f,2.3172786307f,2.3177639729f,2.3182492987f,2.3187346081f,2.3192199012f, + 2.3197051780f,2.3201904384f,2.3206756826f,2.3211609105f,2.3216461221f,2.3221313174f, + 2.3226164965f,2.3231016593f,2.3235868059f,2.3240719362f,2.3245570503f,2.3250421482f, + 2.3255272299f,2.3260122954f,2.3264973447f,2.3269823779f,2.3274673949f,2.3279523957f, + 2.3284373804f,2.3289223489f,2.3294073013f,2.3298922377f,2.3303771579f,2.3308620620f, + 2.3313469500f,2.3318318220f,2.3323166778f,2.3328015177f,2.3332863415f,2.3337711492f, + 2.3342559409f,2.3347407166f,2.3352254763f,2.3357102200f,2.3361949477f,2.3366796595f, + 2.3371643552f,2.3376490350f,2.3381336989f,2.3386183468f,2.3391029788f,2.3395875948f, + 2.3400721950f,2.3405567792f,2.3410413476f,2.3415259001f,2.3420104367f,2.3424949574f, + 2.3429794623f,2.3434639514f,2.3439484246f,2.3444328819f,2.3449173235f,2.3454017493f, + 2.3458861592f,2.3463705534f,2.3468549318f,2.3473392945f,2.3478236413f,2.3483079725f, + 2.3487922879f,2.3492765875f,2.3497608715f,2.3502451397f,2.3507293922f,2.3512136291f, + 2.3516978502f,2.3521820557f,2.3526662455f,2.3531504197f,2.3536345782f,2.3541187211f, + 2.3546028483f,2.3550869600f,2.3555710560f,2.3560551364f,2.3565392013f,2.3570232506f, + 2.3575072843f,2.3579913024f,2.3584753050f,2.3589592920f,2.3594432636f,2.3599272196f, + 2.3604111600f,2.3608950850f,2.3613789945f,2.3618628885f,2.3623467670f,2.3628306301f, + 2.3633144777f,2.3637983098f,2.3642821265f,2.3647659278f,2.3652497136f,2.3657334841f, + 2.3662172391f,2.3667009788f,2.3671847030f,2.3676684119f,2.3681521055f,2.3686357836f, + 2.3691194465f,2.3696030939f,2.3700867261f,2.3705703429f,2.3710539445f,2.3715375307f, + 2.3720211016f,2.3725046573f,2.3729881976f,2.3734717228f,2.3739552326f,2.3744387272f, + 2.3749222066f,2.3754056707f,2.3758891197f,2.3763725534f,2.3768559719f,2.3773393752f, + 2.3778227634f,2.3783061363f,2.3787894941f,2.3792728368f,2.3797561643f,2.3802394766f, + 2.3807227739f,2.3812060560f,2.3816893230f,2.3821725749f,2.3826558117f,2.3831390334f, + 2.3836222401f,2.3841054317f,2.3845886082f,2.3850717697f,2.3855549162f,2.3860380476f, + 2.3865211640f,2.3870042653f,2.3874873517f,2.3879704231f,2.3884534795f,2.3889365209f, + 2.3894195474f,2.3899025589f,2.3903855555f,2.3908685371f,2.3913515038f,2.3918344555f, + 2.3923173924f,2.3928003143f,2.3932832213f,2.3937661135f,2.3942489908f,2.3947318532f, + 2.3952147007f,2.3956975334f,2.3961803513f,2.3966631543f,2.3971459425f,2.3976287159f, + 2.3981114744f,2.3985942182f,2.3990769472f,2.3995596614f,2.4000423608f,2.4005250455f, + 2.4010077154f,2.4014903705f,2.4019730109f,2.4024556366f,2.4029382476f,2.4034208439f, + 2.4039034254f,2.4043859923f,2.4048685445f,2.4053510820f,2.4058336048f,2.4063161130f, + 2.4067986065f,2.4072810854f,2.4077635496f,2.4082459992f,2.4087284343f,2.4092108547f, + 2.4096932605f,2.4101756517f,2.4106580283f,2.4111403903f,2.4116227378f,2.4121050708f, + 2.4125873892f,2.4130696930f,2.4135519823f,2.4140342571f,2.4145165174f,2.4149987632f, + 2.4154809945f,2.4159632113f,2.4164454136f,2.4169276014f,2.4174097748f,2.4178919338f, + 2.4183740783f,2.4188562083f,2.4193383239f,2.4198204251f,2.4203025119f,2.4207845843f, + 2.4212666423f,2.4217486860f,2.4222307152f,2.4227127301f,2.4231947306f,2.4236767167f, + 2.4241586886f,2.4246406460f,2.4251225892f,2.4256045180f,2.4260864326f,2.4265683328f, + 2.4270502187f,2.4275320904f,2.4280139478f,2.4284957909f,2.4289776197f,2.4294594344f, + 2.4299412347f,2.4304230208f,2.4309047927f,2.4313865504f,2.4318682939f,2.4323500232f, + 2.4328317383f,2.4333134392f,2.4337951259f,2.4342767985f,2.4347584569f,2.4352401011f, + 2.4357217312f,2.4362033472f,2.4366849491f,2.4371665368f,2.4376481104f,2.4381296700f, + 2.4386112154f,2.4390927467f,2.4395742640f,2.4400557672f,2.4405372564f,2.4410187315f, + 2.4415001925f,2.4419816395f,2.4424630725f,2.4429444915f,2.4434258965f,2.4439072874f, + 2.4443886644f,2.4448700274f,2.4453513764f,2.4458327114f,2.4463140325f,2.4467953396f, + 2.4472766328f,2.4477579121f,2.4482391774f,2.4487204288f,2.4492016663f,2.4496828899f, + 2.4501640995f,2.4506452953f,2.4511264773f,2.4516076453f,2.4520887995f,2.4525699398f, + 2.4530510663f,2.4535321789f,2.4540132778f,2.4544943627f,2.4549754339f,2.4554564913f, + 2.4559375349f,2.4564185646f,2.4568995806f,2.4573805829f,2.4578615713f,2.4583425460f, + 2.4588235070f,2.4593044542f,2.4597853876f,2.4602663074f,2.4607472134f,2.4612281057f, + 2.4617089843f,2.4621898493f,2.4626707005f,2.4631515380f,2.4636323619f,2.4641131721f, + 2.4645939687f,2.4650747516f,2.4655555209f,2.4660362766f,2.4665170186f,2.4669977470f, + 2.4674784618f,2.4679591630f,2.4684398506f,2.4689205247f,2.4694011851f,2.4698818320f, + 2.4703624653f,2.4708430851f,2.4713236913f,2.4718042840f,2.4722848632f,2.4727654288f, + 2.4732459810f,2.4737265196f,2.4742070447f,2.4746875564f,2.4751680546f,2.4756485392f, + 2.4761290105f,2.4766094682f,2.4770899126f,2.4775703434f,2.4780507609f,2.4785311649f, + 2.4790115555f,2.4794919327f,2.4799722964f,2.4804526468f,2.4809329838f,2.4814133075f, + 2.4818936177f,2.4823739146f,2.4828541981f,2.4833344683f,2.4838147251f,2.4842949686f, + 2.4847751988f,2.4852554156f,2.4857356192f,2.4862158094f,2.4866959864f,2.4871761501f, + 2.4876563004f,2.4881364375f,2.4886165614f,2.4890966720f,2.4895767693f,2.4900568534f, + 2.4905369243f,2.4910169819f,2.4914970263f,2.4919770576f,2.4924570756f,2.4929370804f, + 2.4934170720f,2.4938970504f,2.4943770157f,2.4948569678f,2.4953369068f,2.4958168326f, + 2.4962967452f,2.4967766447f,2.4972565311f,2.4977364044f,2.4982162646f,2.4986961116f, + 2.4991759456f,2.4996557665f,2.5001355743f,2.5006153690f,2.5010951506f,2.5015749192f, + 2.5020546748f,2.5025344172f,2.5030141467f,2.5034938631f,2.5039735665f,2.5044532569f, + 2.5049329343f,2.5054125987f,2.5058922501f,2.5063718885f,2.5068515140f,2.5073311264f, + 2.5078107259f,2.5082903125f,2.5087698861f,2.5092494467f,2.5097289945f,2.5102085293f, + 2.5106880512f,2.5111675601f,2.5116470562f,2.5121265394f,2.5126060097f,2.5130854671f, + 2.5135649117f,2.5140443433f,2.5145237622f,2.5150031681f,2.5154825613f,2.5159619415f, + 2.5164413090f,2.5169206636f,2.5174000055f,2.5178793345f,2.5183586507f,2.5188379541f, + 2.5193172448f,2.5197965227f,2.5202757878f,2.5207550401f,2.5212342797f,2.5217135065f, + 2.5221927206f,2.5226719220f,2.5231511106f,2.5236302865f,2.5241094497f,2.5245886002f, + 2.5250677380f,2.5255468631f,2.5260259756f,2.5265050753f,2.5269841624f,2.5274632368f, + 2.5279422986f,2.5284213477f,2.5289003842f,2.5293794081f,2.5298584193f,2.5303374180f, + 2.5308164040f,2.5312953774f,2.5317743382f,2.5322532864f,2.5327322220f,2.5332111451f, + 2.5336900556f,2.5341689536f,2.5346478389f,2.5351267118f,2.5356055721f,2.5360844199f, + 2.5365632551f,2.5370420779f,2.5375208881f,2.5379996858f,2.5384784711f,2.5389572438f, + 2.5394360041f,2.5399147518f,2.5403934872f,2.5408722100f,2.5413509204f,2.5418296184f, + 2.5423083039f,2.5427869770f,2.5432656377f,2.5437442859f,2.5442229218f,2.5447015452f, + 2.5451801563f,2.5456587549f,2.5461373412f,2.5466159151f,2.5470944767f,2.5475730258f, + 2.5480515626f,2.5485300871f,2.5490085993f,2.5494870991f,2.5499655865f,2.5504440617f, + 2.5509225245f,2.5514009751f,2.5518794133f,2.5523578393f,2.5528362530f,2.5533146544f, + 2.5537930435f,2.5542714204f,2.5547497850f,2.5552281373f,2.5557064775f,2.5561848053f, + 2.5566631210f,2.5571414244f,2.5576197157f,2.5580979947f,2.5585762615f,2.5590545161f, + 2.5595327586f,2.5600109888f,2.5604892069f,2.5609674129f,2.5614456066f,2.5619237882f, + 2.5624019577f,2.5628801150f,2.5633582602f,2.5638363933f,2.5643145143f,2.5647926231f, + 2.5652707199f,2.5657488045f,2.5662268771f,2.5667049376f,2.5671829860f,2.5676610223f, + 2.5681390466f,2.5686170588f,2.5690950590f,2.5695730471f,2.5700510232f,2.5705289873f, + 2.5710069393f,2.5714848794f,2.5719628074f,2.5724407234f,2.5729186274f,2.5733965195f, + 2.5738743995f,2.5743522676f,2.5748301238f,2.5753079679f,2.5757858001f,2.5762636204f, + 2.5767414287f,2.5772192251f,2.5776970096f,2.5781747821f,2.5786525427f,2.5791302915f, + 2.5796080283f,2.5800857532f,2.5805634663f,2.5810411674f,2.5815188567f,2.5819965342f, + 2.5824741997f,2.5829518535f,2.5834294953f,2.5839071254f,2.5843847436f,2.5848623500f, + 2.5853399445f,2.5858175273f,2.5862950982f,2.5867726574f,2.5872502047f,2.5877277403f, + 2.5882052641f,2.5886827761f,2.5891602763f,2.5896377648f,2.5901152415f,2.5905927065f, + 2.5910701598f,2.5915476013f,2.5920250311f,2.5925024492f,2.5929798555f,2.5934572502f, + 2.5939346332f,2.5944120044f,2.5948893640f,2.5953667119f,2.5958440481f,2.5963213727f, + 2.5967986856f,2.5972759868f,2.5977532764f,2.5982305544f,2.5987078207f,2.5991850754f, + 2.5996623185f,2.6001395500f,2.6006167698f,2.6010939781f,2.6015711747f,2.6020483598f, + 2.6025255333f,2.6030026952f,2.6034798456f,2.6039569844f,2.6044341116f,2.6049112273f, + 2.6053883314f,2.6058654240f,2.6063425051f,2.6068195746f,2.6072966327f,2.6077736792f, + 2.6082507142f,2.6087277377f,2.6092047498f,2.6096817503f,2.6101587394f,2.6106357170f, + 2.6111126831f,2.6115896378f,2.6120665810f,2.6125435128f,2.6130204332f,2.6134973421f, + 2.6139742396f,2.6144511256f,2.6149280003f,2.6154048635f,2.6158817154f,2.6163585558f, + 2.6168353849f,2.6173122025f,2.6177890088f,2.6182658038f,2.6187425873f,2.6192193596f, + 2.6196961204f,2.6201728699f,2.6206496081f,2.6211263350f,2.6216030505f,2.6220797547f, + 2.6225564476f,2.6230331292f,2.6235097995f,2.6239864585f,2.6244631062f,2.6249397427f, + 2.6254163678f,2.6258929817f,2.6263695844f,2.6268461758f,2.6273227559f,2.6277993248f, + 2.6282758824f,2.6287524289f,2.6292289641f,2.6297054881f,2.6301820008f,2.6306585024f, + 2.6311349928f,2.6316114720f,2.6320879399f,2.6325643968f,2.6330408424f,2.6335172769f, + 2.6339937002f,2.6344701124f,2.6349465134f,2.6354229032f,2.6358992820f,2.6363756496f, + 2.6368520061f,2.6373283514f,2.6378046857f,2.6382810088f,2.6387573209f,2.6392336218f, + 2.6397099117f,2.6401861905f,2.6406624582f,2.6411387149f,2.6416149604f,2.6420911950f, + 2.6425674185f,2.6430436309f,2.6435198323f,2.6439960227f,2.6444722020f,2.6449483704f, + 2.6454245277f,2.6459006740f,2.6463768093f,2.6468529336f,2.6473290470f,2.6478051493f, + 2.6482812407f,2.6487573211f,2.6492333905f,2.6497094490f,2.6501854966f,2.6506615332f, + 2.6511375588f,2.6516135735f,2.6520895773f,2.6525655702f,2.6530415521f,2.6535175232f, + 2.6539934833f,2.6544694326f,2.6549453710f,2.6554212984f,2.6558972150f,2.6563731208f, + 2.6568490156f,2.6573248996f,2.6578007728f,2.6582766351f,2.6587524865f,2.6592283271f, + 2.6597041569f,2.6601799759f,2.6606557840f,2.6611315814f,2.6616073679f,2.6620831437f, + 2.6625589086f,2.6630346627f,2.6635104061f,2.6639861387f,2.6644618605f,2.6649375716f, + 2.6654132719f,2.6658889614f,2.6663646403f,2.6668403083f,2.6673159657f,2.6677916123f, + 2.6682672481f,2.6687428733f,2.6692184878f,2.6696940915f,2.6701696846f,2.6706452669f, + 2.6711208386f,2.6715963996f,2.6720719499f,2.6725474896f,2.6730230186f,2.6734985369f, + 2.6739740446f,2.6744495416f,2.6749250280f,2.6754005038f,2.6758759689f,2.6763514234f, + 2.6768268673f,2.6773023006f,2.6777777233f,2.6782531354f,2.6787285369f,2.6792039278f, + 2.6796793081f,2.6801546779f,2.6806300371f,2.6811053857f,2.6815807238f,2.6820560513f, + 2.6825313683f,2.6830066747f,2.6834819706f,2.6839572560f,2.6844325308f,2.6849077951f, + 2.6853830490f,2.6858582923f,2.6863335251f,2.6868087474f,2.6872839593f,2.6877591606f, + 2.6882343515f,2.6887095319f,2.6891847019f,2.6896598614f,2.6901350104f,2.6906101490f, + 2.6910852772f,2.6915603949f,2.6920355022f,2.6925105991f,2.6929856855f,2.6934607615f, + 2.6939358272f,2.6944108824f,2.6948859272f,2.6953609617f,2.6958359858f,2.6963109995f, + 2.6967860028f,2.6972609957f,2.6977359783f,2.6982109505f,2.6986859124f,2.6991608640f, + 2.6996358052f,2.7001107361f,2.7005856566f,2.7010605668f,2.7015354667f,2.7020103564f, + 2.7024852357f,2.7029601047f,2.7034349634f,2.7039098118f,2.7043846499f,2.7048594778f, + 2.7053342954f,2.7058091028f,2.7062838998f,2.7067586867f,2.7072334632f,2.7077082296f, + 2.7081829857f,2.7086577316f,2.7091324672f,2.7096071927f,2.7100819079f,2.7105566129f, + 2.7110313077f,2.7115059923f,2.7119806668f,2.7124553310f,2.7129299851f,2.7134046290f, + 2.7138792627f,2.7143538862f,2.7148284996f,2.7153031029f,2.7157776960f,2.7162522790f, + 2.7167268518f,2.7172014145f,2.7176759671f,2.7181505095f,2.7186250419f,2.7190995641f, + 2.7195740763f,2.7200485783f,2.7205230703f,2.7209975522f,2.7214720240f,2.7219464857f, + 2.7224209374f,2.7228953789f,2.7233698105f,2.7238442320f,2.7243186434f,2.7247930448f, + 2.7252674362f,2.7257418175f,2.7262161888f,2.7266905501f,2.7271649014f,2.7276392427f, + 2.7281135740f,2.7285878952f,2.7290622065f,2.7295365078f,2.7300107992f,2.7304850805f, + 2.7309593519f,2.7314336133f,2.7319078648f,2.7323821063f,2.7328563379f,2.7333305595f, + 2.7338047712f,2.7342789729f,2.7347531648f,2.7352273467f,2.7357015187f,2.7361756808f, + 2.7366498329f,2.7371239752f,2.7375981076f,2.7380722301f,2.7385463428f,2.7390204455f, + 2.7394945384f,2.7399686214f,2.7404426946f,2.7409167579f,2.7413908113f,2.7418648549f, + 2.7423388887f,2.7428129127f,2.7432869268f,2.7437609311f,2.7442349255f,2.7447089102f, + 2.7451828850f,2.7456568501f,2.7461308054f,2.7466047508f,2.7470786865f,2.7475526124f, + 2.7480265286f,2.7485004349f,2.7489743315f,2.7494482184f,2.7499220955f,2.7503959628f, + 2.7508698204f,2.7513436683f,2.7518175064f,2.7522913348f,2.7527651535f,2.7532389625f, + 2.7537127618f,2.7541865513f,2.7546603312f,2.7551341014f,2.7556078619f,2.7560816127f, + 2.7565553538f,2.7570290852f,2.7575028070f,2.7579765191f,2.7584502216f,2.7589239144f, + 2.7593975976f,2.7598712711f,2.7603449350f,2.7608185892f,2.7612922339f,2.7617658689f, + 2.7622394943f,2.7627131101f,2.7631867162f,2.7636603128f,2.7641338998f,2.7646074772f, + 2.7650810450f,2.7655546033f,2.7660281520f,2.7665016911f,2.7669752206f,2.7674487406f, + 2.7679222510f,2.7683957519f,2.7688692432f,2.7693427251f,2.7698161973f,2.7702896601f, + 2.7707631133f,2.7712365570f,2.7717099912f,2.7721834159f,2.7726568311f,2.7731302368f, + 2.7736036330f,2.7740770198f,2.7745503970f,2.7750237648f,2.7754971231f,2.7759704720f, + 2.7764438114f,2.7769171413f,2.7773904618f,2.7778637728f,2.7783370744f,2.7788103666f, + 2.7792836493f,2.7797569227f,2.7802301866f,2.7807034411f,2.7811766862f,2.7816499218f, + 2.7821231481f,2.7825963650f,2.7830695725f,2.7835427707f,2.7840159594f,2.7844891388f, + 2.7849623088f,2.7854354695f,2.7859086208f,2.7863817627f,2.7868548953f,2.7873280186f, + 2.7878011325f,2.7882742371f,2.7887473324f,2.7892204183f,2.7896934949f,2.7901665623f, + 2.7906396203f,2.7911126690f,2.7915857084f,2.7920587386f,2.7925317594f,2.7930047710f, + 2.7934777733f,2.7939507663f,2.7944237501f,2.7948967246f,2.7953696898f,2.7958426458f, + 2.7963155925f,2.7967885301f,2.7972614583f,2.7977343774f,2.7982072872f,2.7986801878f, + 2.7991530792f,2.7996259614f,2.8000988344f,2.8005716981f,2.8010445527f,2.8015173981f, + 2.8019902343f,2.8024630613f,2.8029358792f,2.8034086879f,2.8038814874f,2.8043542778f, + 2.8048270590f,2.8052998310f,2.8057725939f,2.8062453477f,2.8067180923f,2.8071908278f, + 2.8076635542f,2.8081362715f,2.8086089796f,2.8090816787f,2.8095543686f,2.8100270494f, + 2.8104997212f,2.8109723838f,2.8114450374f,2.8119176819f,2.8123903173f,2.8128629436f, + 2.8133355609f,2.8138081691f,2.8142807682f,2.8147533583f,2.8152259394f,2.8156985114f, + 2.8161710744f,2.8166436283f,2.8171161732f,2.8175887091f,2.8180612360f,2.8185337539f, + 2.8190062628f,2.8194787626f,2.8199512535f,2.8204237354f,2.8208962083f,2.8213686722f, + 2.8218411271f,2.8223135731f,2.8227860100f,2.8232584381f,2.8237308571f,2.8242032672f, + 2.8246756684f,2.8251480606f,2.8256204439f,2.8260928183f,2.8265651837f,2.8270375402f, + 2.8275098877f,2.8279822264f,2.8284545562f,2.8289268770f,2.8293991890f,2.8298714920f, + 2.8303437862f,2.8308160714f,2.8312883478f,2.8317606154f,2.8322328740f,2.8327051238f, + 2.8331773647f,2.8336495968f,2.8341218200f,2.8345940343f,2.8350662399f,2.8355384365f, + 2.8360106244f,2.8364828034f,2.8369549736f,2.8374271350f,2.8378992876f,2.8383714313f, + 2.8388435663f,2.8393156924f,2.8397878098f,2.8402599184f,2.8407320181f,2.8412041092f, + 2.8416761914f,2.8421482648f,2.8426203295f,2.8430923855f,2.8435644326f,2.8440364711f, + 2.8445085007f,2.8449805217f,2.8454525338f,2.8459245373f,2.8463965320f,2.8468685181f, + 2.8473404953f,2.8478124639f,2.8482844238f,2.8487563749f,2.8492283174f,2.8497002512f, + 2.8501721762f,2.8506440926f,2.8511160003f,2.8515878994f,2.8520597897f,2.8525316714f, + 2.8530035444f,2.8534754088f,2.8539472645f,2.8544191116f,2.8548909500f,2.8553627798f, + 2.8558346010f,2.8563064135f,2.8567782174f,2.8572500126f,2.8577217993f,2.8581935774f, + 2.8586653468f,2.8591371076f,2.8596088599f,2.8600806035f,2.8605523386f,2.8610240651f, + 2.8614957830f,2.8619674923f,2.8624391930f,2.8629108852f,2.8633825688f,2.8638542439f, + 2.8643259104f,2.8647975684f,2.8652692179f,2.8657408587f,2.8662124911f,2.8666841149f, + 2.8671557303f,2.8676273370f,2.8680989353f,2.8685705251f,2.8690421064f,2.8695136791f, + 2.8699852434f,2.8704567992f,2.8709283464f,2.8713998853f,2.8718714156f,2.8723429374f, + 2.8728144508f,2.8732859557f,2.8737574522f,2.8742289402f,2.8747004198f,2.8751718909f, + 2.8756433536f,2.8761148078f,2.8765862536f,2.8770576910f,2.8775291199f,2.8780005404f, + 2.8784719526f,2.8789433563f,2.8794147516f,2.8798861385f,2.8803575170f,2.8808288871f, + 2.8813002488f,2.8817716022f,2.8822429472f,2.8827142837f,2.8831856120f,2.8836569318f, + 2.8841282433f,2.8845995465f,2.8850708413f,2.8855421277f,2.8860134058f,2.8864846756f, + 2.8869559371f,2.8874271902f,2.8878984349f,2.8883696714f,2.8888408996f,2.8893121194f, + 2.8897833309f,2.8902545342f,2.8907257291f,2.8911969158f,2.8916680941f,2.8921392642f, + 2.8926104260f,2.8930815795f,2.8935527247f,2.8940238617f,2.8944949904f,2.8949661109f, + 2.8954372231f,2.8959083271f,2.8963794228f,2.8968505103f,2.8973215895f,2.8977926605f, + 2.8982637233f,2.8987347779f,2.8992058242f,2.8996768624f,2.9001478923f,2.9006189140f, + 2.9010899275f,2.9015609329f,2.9020319300f,2.9025029190f,2.9029738997f,2.9034448723f, + 2.9039158367f,2.9043867930f,2.9048577411f,2.9053286810f,2.9057996128f,2.9062705364f, + 2.9067414519f,2.9072123592f,2.9076832584f,2.9081541495f,2.9086250324f,2.9090959072f, + 2.9095667739f,2.9100376324f,2.9105084829f,2.9109793252f,2.9114501595f,2.9119209856f, + 2.9123918037f,2.9128626136f,2.9133334155f,2.9138042093f,2.9142749950f,2.9147457727f, + 2.9152165423f,2.9156873038f,2.9161580572f,2.9166288026f,2.9170995400f,2.9175702693f, + 2.9180409905f,2.9185117037f,2.9189824089f,2.9194531061f,2.9199237952f,2.9203944763f, + 2.9208651494f,2.9213358145f,2.9218064715f,2.9222771206f,2.9227477617f,2.9232183947f, + 2.9236890198f,2.9241596369f,2.9246302460f,2.9251008472f,2.9255714403f,2.9260420255f, + 2.9265126027f,2.9269831720f,2.9274537333f,2.9279242867f,2.9283948321f,2.9288653695f, + 2.9293358990f,2.9298064206f,2.9302769343f,2.9307474400f,2.9312179378f,2.9316884277f, + 2.9321589097f,2.9326293838f,2.9330998499f,2.9335703082f,2.9340407585f,2.9345112010f, + 2.9349816356f,2.9354520623f,2.9359224811f,2.9363928920f,2.9368632951f,2.9373336903f, + 2.9378040776f,2.9382744571f,2.9387448287f,2.9392151925f,2.9396855484f,2.9401558965f, + 2.9406262367f,2.9410965691f,2.9415668937f,2.9420372104f,2.9425075194f,2.9429778205f, + 2.9434481138f,2.9439183993f,2.9443886770f,2.9448589469f,2.9453292089f,2.9457994632f, + 2.9462697097f,2.9467399485f,2.9472101794f,2.9476804026f,2.9481506180f,2.9486208256f, + 2.9490910255f,2.9495612176f,2.9500314020f,2.9505015786f,2.9509717474f,2.9514419085f, + 2.9519120619f,2.9523822075f,2.9528523455f,2.9533224757f,2.9537925981f,2.9542627129f, + 2.9547328199f,2.9552029192f,2.9556730108f,2.9561430948f,2.9566131710f,2.9570832395f, + 2.9575533004f,2.9580233535f,2.9584933990f,2.9589634368f,2.9594334669f,2.9599034894f, + 2.9603735042f,2.9608435113f,2.9613135108f,2.9617835026f,2.9622534868f,2.9627234633f, + 2.9631934322f,2.9636633935f,2.9641333471f,2.9646032931f,2.9650732315f,2.9655431623f, + 2.9660130854f,2.9664830010f,2.9669529089f,2.9674228092f,2.9678927020f,2.9683625871f, + 2.9688324646f,2.9693023346f,2.9697721970f,2.9702420518f,2.9707118990f,2.9711817386f, + 2.9716515707f,2.9721213952f,2.9725912122f,2.9730610216f,2.9735308235f,2.9740006178f, + 2.9744704046f,2.9749401838f,2.9754099555f,2.9758797197f,2.9763494763f,2.9768192254f, + 2.9772889670f,2.9777587011f,2.9782284277f,2.9786981468f,2.9791678584f,2.9796375624f, + 2.9801072590f,2.9805769481f,2.9810466297f,2.9815163038f,2.9819859705f,2.9824556296f, + 2.9829252814f,2.9833949256f,2.9838645624f,2.9843341917f,2.9848038135f,2.9852734280f, + 2.9857430349f,2.9862126344f,2.9866822265f,2.9871518112f,2.9876213884f,2.9880909582f, + 2.9885605206f,2.9890300755f,2.9894996231f,2.9899691632f,2.9904386959f,2.9909082213f, + 2.9913777392f,2.9918472497f,2.9923167529f,2.9927862486f,2.9932557370f,2.9937252180f, + 2.9941946916f,2.9946641578f,2.9951336167f,2.9956030682f,2.9960725124f,2.9965419492f, + 2.9970113786f,2.9974808007f,2.9979502155f,2.9984196229f,2.9988890230f,2.9993584158f, + 2.9998278012f,3.0002971793f,3.0007665501f,3.0012359136f,3.0017052697f,3.0021746186f, + 3.0026439601f,3.0031132943f,3.0035826213f,3.0040519409f,3.0045212533f,3.0049905584f, + 3.0054598562f,3.0059291467f,3.0063984300f,3.0068677059f,3.0073369747f,3.0078062361f, + 3.0082754903f,3.0087447372f,3.0092139769f,3.0096832094f,3.0101524346f,3.0106216526f, + 3.0110908633f,3.0115600668f,3.0120292631f,3.0124984521f,3.0129676339f,3.0134368086f, + 3.0139059760f,3.0143751362f,3.0148442892f,3.0153134350f,3.0157825736f,3.0162517050f, + 3.0167208292f,3.0171899462f,3.0176590561f,3.0181281587f,3.0185972542f,3.0190663426f, + 3.0195354238f,3.0200044978f,3.0204735646f,3.0209426243f,3.0214116769f,3.0218807223f, + 3.0223497605f,3.0228187916f,3.0232878156f,3.0237568325f,3.0242258422f,3.0246948448f, + 3.0251638403f,3.0256328286f,3.0261018099f,3.0265707841f,3.0270397511f,3.0275087110f, + 3.0279776639f,3.0284466096f,3.0289155483f,3.0293844799f,3.0298534044f,3.0303223218f, + 3.0307912321f,3.0312601354f,3.0317290316f,3.0321979208f,3.0326668028f,3.0331356779f, + 3.0336045458f,3.0340734068f,3.0345422607f,3.0350111075f,3.0354799473f,3.0359487801f, + 3.0364176058f,3.0368864246f,3.0373552363f,3.0378240409f,3.0382928386f,3.0387616293f, + 3.0392304129f,3.0396991896f,3.0401679592f,3.0406367219f,3.0411054775f,3.0415742262f, + 3.0420429679f,3.0425117026f,3.0429804303f,3.0434491511f,3.0439178649f,3.0443865717f, + 3.0448552716f,3.0453239645f,3.0457926504f,3.0462613294f,3.0467300015f,3.0471986666f, + 3.0476673247f,3.0481359760f,3.0486046203f,3.0490732576f,3.0495418881f,3.0500105116f, + 3.0504791282f,3.0509477379f,3.0514163407f,3.0518849366f,3.0523535255f,3.0528221076f, + 3.0532906828f,3.0537592511f,3.0542278125f,3.0546963670f,3.0551649146f,3.0556334554f, + 3.0561019892f,3.0565705162f,3.0570390364f,3.0575075497f,3.0579760561f,3.0584445557f, + 3.0589130484f,3.0593815342f,3.0598500133f,3.0603184855f,3.0607869508f,3.0612554093f, + 3.0617238610f,3.0621923058f,3.0626607439f,3.0631291751f,3.0635975995f,3.0640660171f, + 3.0645344279f,3.0650028318f,3.0654712290f,3.0659396194f,3.0664080030f,3.0668763798f, + 3.0673447498f,3.0678131130f,3.0682814695f,3.0687498191f,3.0692181620f,3.0696864982f, + 3.0701548275f,3.0706231501f,3.0710914660f,3.0715597751f,3.0720280774f,3.0724963730f, + 3.0729646619f,3.0734329440f,3.0739012194f,3.0743694880f,3.0748377499f,3.0753060051f, + 3.0757742536f,3.0762424953f,3.0767107304f,3.0771789587f,3.0776471803f,3.0781153952f, + 3.0785836035f,3.0790518050f,3.0795199998f,3.0799881879f,3.0804563694f,3.0809245442f, + 3.0813927122f,3.0818608737f,3.0823290284f,3.0827971765f,3.0832653179f,3.0837334526f, + 3.0842015807f,3.0846697022f,3.0851378170f,3.0856059251f,3.0860740266f,3.0865421215f, + 3.0870102097f,3.0874782913f,3.0879463662f,3.0884144346f,3.0888824963f,3.0893505514f, + 3.0898185999f,3.0902866417f,3.0907546770f,3.0912227056f,3.0916907277f,3.0921587431f, + 3.0926267520f,3.0930947543f,3.0935627500f,3.0940307391f,3.0944987216f,3.0949666975f, + 3.0954346669f,3.0959026297f,3.0963705859f,3.0968385356f,3.0973064787f,3.0977744153f, + 3.0982423453f,3.0987102688f,3.0991781857f,3.0996460960f,3.1001139999f,3.1005818972f, + 3.1010497879f,3.1015176722f,3.1019855499f,3.1024534211f,3.1029212858f,3.1033891439f, + 3.1038569956f,3.1043248407f,3.1047926794f,3.1052605115f,3.1057283372f,3.1061961563f, + 3.1066639690f,3.1071317752f,3.1075995749f,3.1080673681f,3.1085351548f,3.1090029351f, + 3.1094707089f,3.1099384762f,3.1104062371f,3.1108739915f,3.1113417395f,3.1118094810f, + 3.1122772161f,3.1127449447f,3.1132126669f,3.1136803826f,3.1141480919f,3.1146157948f, + 3.1150834912f,3.1155511812f,3.1160188648f,3.1164865420f,3.1169542128f,3.1174218771f, + 3.1178895351f,3.1183571866f,3.1188248318f,3.1192924705f,3.1197601029f,3.1202277288f, + 3.1206953484f,3.1211629616f,3.1216305684f,3.1220981689f,3.1225657629f,3.1230333506f, + 3.1235009319f,3.1239685069f,3.1244360755f,3.1249036378f,3.1253711937f,3.1258387432f, + 3.1263062864f,3.1267738233f,3.1272413538f,3.1277088780f,3.1281763958f,3.1286439073f, + 3.1291114125f,3.1295789114f,3.1300464040f,3.1305138902f,3.1309813701f,3.1314488437f, + 3.1319163111f,3.1323837721f,3.1328512268f,3.1333186752f,3.1337861173f,3.1342535531f, + 3.1347209827f,3.1351884060f,3.1356558229f,3.1361232337f,3.1365906381f,3.1370580363f, + 3.1375254282f,3.1379928138f,3.1384601932f,3.1389275663f,3.1393949332f,3.1398622938f, + 3.1403296482f,3.1407969963f,3.1412643382f,3.1417316739f,3.1421990033f,3.1426663265f, + 3.1431336435f,3.1436009543f,3.1440682588f,3.1445355571f,3.1450028492f,3.1454701351f, + 3.1459374148f,3.1464046883f,3.1468719556f,3.1473392167f,3.1478064716f,3.1482737203f, + 3.1487409628f,3.1492081992f,3.1496754294f,3.1501426533f,3.1506098712f,3.1510770828f, + 3.1515442883f,3.1520114876f,3.1524786808f,3.1529458678f,3.1534130486f,3.1538802233f, + 3.1543473919f,3.1548145543f,3.1552817106f,3.1557488607f,3.1562160047f,3.1566831425f, + 3.1571502743f,3.1576173999f,3.1580845194f,3.1585516328f,3.1590187401f,3.1594858412f, + 3.1599529363f,3.1604200252f,3.1608871080f,3.1613541848f,3.1618212554f,3.1622883200f, + 3.1627553785f,3.1632224309f,3.1636894772f,3.1641565174f,3.1646235515f,3.1650905796f, + 3.1655576016f,3.1660246176f,3.1664916275f,3.1669586313f,3.1674256291f,3.1678926208f, + 3.1683596064f,3.1688265861f,3.1692935596f,3.1697605272f,3.1702274887f,3.1706944442f, + 3.1711613936f,3.1716283370f,3.1720952744f,3.1725622058f,3.1730291311f,3.1734960505f, + 3.1739629638f,3.1744298711f,3.1748967724f,3.1753636678f,3.1758305571f,3.1762974404f, + 3.1767643177f,3.1772311891f,3.1776980544f,3.1781649138f,3.1786317672f,3.1790986146f, + 3.1795654561f,3.1800322916f,3.1804991211f,3.1809659446f,3.1814327622f,3.1818995739f, + 3.1823663795f,3.1828331793f,3.1832999731f,3.1837667609f,3.1842335428f,3.1847003188f, + 3.1851670888f,3.1856338529f,3.1861006111f,3.1865673633f,3.1870341096f,3.1875008501f, + 3.1879675845f,3.1884343131f,3.1889010358f,3.1893677526f,3.1898344634f,3.1903011684f, + 3.1907678674f,3.1912345606f,3.1917012479f,3.1921679293f,3.1926346048f,3.1931012744f, + 3.1935679382f,3.1940345961f,3.1945012481f,3.1949678942f,3.1954345345f,3.1959011689f, + 3.1963677974f,3.1968344201f,3.1973010370f,3.1977676480f,3.1982342531f,3.1987008524f, + 3.1991674459f,3.1996340335f,3.2001006153f,3.2005671913f,3.2010337614f,3.2015003257f, + 3.2019668842f,3.2024334369f,3.2028999837f,3.2033665248f,3.2038330600f,3.2042995894f, + 3.2047661131f,3.2052326309f,3.2056991429f,3.2061656492f,3.2066321496f,3.2070986443f, + 3.2075651331f,3.2080316162f,3.2084980935f,3.2089645651f,3.2094310308f,3.2098974908f, + 3.2103639451f,3.2108303935f,3.2112968363f,3.2117632732f,3.2122297044f,3.2126961299f, + 3.2131625496f,3.2136289635f,3.2140953717f,3.2145617742f,3.2150281709f,3.2154945620f, + 3.2159609472f,3.2164273268f,3.2168937006f,3.2173600687f,3.2178264311f,3.2182927878f, + 3.2187591388f,3.2192254840f,3.2196918236f,3.2201581575f,3.2206244856f,3.2210908081f, + 3.2215571248f,3.2220234359f,3.2224897413f,3.2229560410f,3.2234223351f,3.2238886234f, + 3.2243549061f,3.2248211831f,3.2252874544f,3.2257537201f,3.2262199801f,3.2266862345f, + 3.2271524832f,3.2276187262f,3.2280849636f,3.2285511953f,3.2290174214f,3.2294836419f, + 3.2299498567f,3.2304160659f,3.2308822695f,3.2313484674f,3.2318146597f,3.2322808464f, + 3.2327470274f,3.2332132029f,3.2336793727f,3.2341455369f,3.2346116955f,3.2350778485f, + 3.2355439959f,3.2360101377f,3.2364762739f,3.2369424045f,3.2374085295f,3.2378746490f, + 3.2383407628f,3.2388068711f,3.2392729738f,3.2397390709f,3.2402051624f,3.2406712484f, + 3.2411373288f,3.2416034037f,3.2420694729f,3.2425355367f,3.2430015948f,3.2434676475f, + 3.2439336945f,3.2443997361f,3.2448657720f,3.2453318025f,3.2457978274f,3.2462638468f, + 3.2467298606f,3.2471958689f,3.2476618717f,3.2481278690f,3.2485938607f,3.2490598470f, + 3.2495258277f,3.2499918029f,3.2504577726f,3.2509237368f,3.2513896955f,3.2518556487f, + 3.2523215964f,3.2527875386f,3.2532534753f,3.2537194066f,3.2541853323f,3.2546512526f, + 3.2551171674f,3.2555830767f,3.2560489806f,3.2565148789f,3.2569807719f,3.2574466593f, + 3.2579125413f,3.2583784178f,3.2588442889f,3.2593101545f,3.2597760147f,3.2602418694f, + 3.2607077187f,3.2611735626f,3.2616394010f,3.2621052340f,3.2625710615f,3.2630368836f, + 3.2635027003f,3.2639685116f,3.2644343174f,3.2649001179f,3.2653659129f,3.2658317025f, + 3.2662974867f,3.2667632655f,3.2672290389f,3.2676948069f,3.2681605695f,3.2686263267f, + 3.2690920785f,3.2695578250f,3.2700235660f,3.2704893017f,3.2709550320f,3.2714207569f, + 3.2718864764f,3.2723521906f,3.2728178994f,3.2732836028f,3.2737493009f,3.2742149936f, + 3.2746806809f,3.2751463629f,3.2756120396f,3.2760777109f,3.2765433768f,3.2770090375f, + 3.2774746927f,3.2779403427f,3.2784059873f,3.2788716266f,3.2793372605f,3.2798028891f, + 3.2802685124f,3.2807341304f,3.2811997431f,3.2816653505f,3.2821309525f,3.2825965492f, + 3.2830621407f,3.2835277268f,3.2839933076f,3.2844588832f,3.2849244534f,3.2853900184f, + 3.2858555781f,3.2863211324f,3.2867866815f,3.2872522254f,3.2877177639f,3.2881832972f, + 3.2886488252f,3.2891143479f,3.2895798654f,3.2900453776f,3.2905108845f,3.2909763862f, + 3.2914418826f,3.2919073738f,3.2923728598f,3.2928383405f,3.2933038159f,3.2937692861f, + 3.2942347511f,3.2947002108f,3.2951656653f,3.2956311146f,3.2960965587f,3.2965619975f, + 3.2970274311f,3.2974928595f,3.2979582827f,3.2984237006f,3.2988891134f,3.2993545210f, + 3.2998199233f,3.3002853205f,3.3007507124f,3.3012160992f,3.3016814807f,3.3021468571f, + 3.3026122283f,3.3030775943f,3.3035429551f,3.3040083108f,3.3044736612f,3.3049390065f, + 3.3054043467f,3.3058696816f,3.3063350114f,3.3068003360f,3.3072656555f,3.3077309698f, + 3.3081962790f,3.3086615830f,3.3091268819f,3.3095921756f,3.3100574642f,3.3105227476f, + 3.3109880259f,3.3114532991f,3.3119185672f,3.3123838301f,3.3128490879f,3.3133143405f, + 3.3137795881f,3.3142448305f,3.3147100678f,3.3151753000f,3.3156405271f,3.3161057491f, + 3.3165709660f,3.3170361777f,3.3175013844f,3.3179665860f,3.3184317825f,3.3188969739f, + 3.3193621602f,3.3198273414f,3.3202925176f,3.3207576887f,3.3212228547f,3.3216880156f, + 3.3221531714f,3.3226183222f,3.3230834679f,3.3235486086f,3.3240137442f,3.3244788747f, + 3.3249440002f,3.3254091206f,3.3258742360f,3.3263393463f,3.3268044516f,3.3272695519f, + 3.3277346471f,3.3281997373f,3.3286648224f,3.3291299025f,3.3295949776f,3.3300600477f, + 3.3305251127f,3.3309901727f,3.3314552277f,3.3319202777f,3.3323853227f,3.3328503627f, + 3.3333153977f,3.3337804276f,3.3342454526f,3.3347104725f,3.3351754875f,3.3356404975f, + 3.3361055025f,3.3365705025f,3.3370354975f,3.3375004875f,3.3379654726f,3.3384304526f, + 3.3388954278f,3.3393603979f,3.3398253631f,3.3402903233f,3.3407552785f,3.3412202288f, + 3.3416851741f,3.3421501145f,3.3426150499f,3.3430799803f,3.3435449058f,3.3440098264f, + 3.3444747421f,3.3449396527f,3.3454045585f,3.3458694593f,3.3463343552f,3.3467992462f, + 3.3472641322f,3.3477290133f,3.3481938895f,3.3486587608f,3.3491236272f,3.3495884886f, + 3.3500533451f,3.3505181968f,3.3509830435f,3.3514478853f,3.3519127223f,3.3523775543f, + 3.3528423814f,3.3533072037f,3.3537720210f,3.3542368335f,3.3547016411f,3.3551664438f, + 3.3556312416f,3.3560960346f,3.3565608227f,3.3570256059f,3.3574903842f,3.3579551577f, + 3.3584199263f,3.3588846901f,3.3593494490f,3.3598142030f,3.3602789522f,3.3607436966f, + 3.3612084361f,3.3616731707f,3.3621379005f,3.3626026255f,3.3630673457f,3.3635320610f, + 3.3639967714f,3.3644614771f,3.3649261779f,3.3653908739f,3.3658555651f,3.3663202514f, + 3.3667849330f,3.3672496097f,3.3677142816f,3.3681789488f,3.3686436111f,3.3691082686f, + 3.3695729213f,3.3700375692f,3.3705022123f,3.3709668506f,3.3714314842f,3.3718961129f, + 3.3723607369f,3.3728253561f,3.3732899705f,3.3737545801f,3.3742191850f,3.3746837851f, + 3.3751483804f,3.3756129709f,3.3760775567f,3.3765421377f,3.3770067140f,3.3774712855f, + 3.3779358523f,3.3784004143f,3.3788649715f,3.3793295240f,3.3797940718f,3.3802586148f, + 3.3807231531f,3.3811876867f,3.3816522155f,3.3821167396f,3.3825812590f,3.3830457736f, + 3.3835102836f,3.3839747888f,3.3844392892f,3.3849037850f,3.3853682761f,3.3858327624f, + 3.3862972441f,3.3867617210f,3.3872261932f,3.3876906608f,3.3881551236f,3.3886195817f, + 3.3890840352f,3.3895484839f,3.3900129280f,3.3904773674f,3.3909418021f,3.3914062322f, + 3.3918706575f,3.3923350782f,3.3927994942f,3.3932639055f,3.3937283122f,3.3941927142f, + 3.3946571116f,3.3951215043f,3.3955858923f,3.3960502757f,3.3965146544f,3.3969790285f, + 3.3974433979f,3.3979077627f,3.3983721229f,3.3988364784f,3.3993008292f,3.3997651755f, + 3.4002295171f,3.4006938541f,3.4011581864f,3.4016225142f,3.4020868373f,3.4025511558f, + 3.4030154696f,3.4034797789f,3.4039440835f,3.4044083836f,3.4048726790f,3.4053369698f, + 3.4058012561f,3.4062655377f,3.4067298147f,3.4071940872f,3.4076583550f,3.4081226183f, + 3.4085868770f,3.4090511311f,3.4095153806f,3.4099796255f,3.4104438659f,3.4109081017f, + 3.4113723329f,3.4118365595f,3.4123007816f,3.4127649991f,3.4132292121f,3.4136934205f, + 3.4141576243f,3.4146218236f,3.4150860183f,3.4155502085f,3.4160143941f,3.4164785752f, + 3.4169427518f,3.4174069238f,3.4178710913f,3.4183352542f,3.4187994126f,3.4192635665f, + 3.4197277159f,3.4201918607f,3.4206560010f,3.4211201368f,3.4215842681f,3.4220483948f, + 3.4225125171f,3.4229766348f,3.4234407480f,3.4239048568f,3.4243689610f,3.4248330607f, + 3.4252971560f,3.4257612467f,3.4262253329f,3.4266894147f,3.4271534919f,3.4276175647f, + 3.4280816330f,3.4285456968f,3.4290097562f,3.4294738110f,3.4299378614f,3.4304019073f, + 3.4308659488f,3.4313299858f,3.4317940183f,3.4322580464f,3.4327220700f,3.4331860891f, + 3.4336501038f,3.4341141141f,3.4345781199f,3.4350421212f,3.4355061181f,3.4359701106f, + 3.4364340986f,3.4368980822f,3.4373620614f,3.4378260361f,3.4382900064f,3.4387539723f, + 3.4392179337f,3.4396818907f,3.4401458433f,3.4406097915f,3.4410737353f,3.4415376746f, + 3.4420016096f,3.4424655401f,3.4429294663f,3.4433933880f,3.4438573054f,3.4443212183f, + 3.4447851268f,3.4452490310f,3.4457129307f,3.4461768261f,3.4466407171f,3.4471046037f, + 3.4475684859f,3.4480323638f,3.4484962372f,3.4489601063f,3.4494239710f,3.4498878314f, + 3.4503516874f,3.4508155390f,3.4512793863f,3.4517432292f,3.4522070677f,3.4526709019f, + 3.4531347317f,3.4535985572f,3.4540623784f,3.4545261951f,3.4549900076f,3.4554538157f, + 3.4559176195f,3.4563814189f,3.4568452140f,3.4573090048f,3.4577727912f,3.4582365733f, + 3.4587003511f,3.4591641246f,3.4596278938f,3.4600916586f,3.4605554191f,3.4610191753f, + 3.4614829272f,3.4619466748f,3.4624104181f,3.4628741571f,3.4633378918f,3.4638016222f, + 3.4642653483f,3.4647290701f,3.4651927876f,3.4656565008f,3.4661202097f,3.4665839144f, + 3.4670476147f,3.4675113108f,3.4679750026f,3.4684386902f,3.4689023734f,3.4693660524f, + 3.4698297272f,3.4702933976f,3.4707570638f,3.4712207258f,3.4716843834f,3.4721480369f, + 3.4726116860f,3.4730753310f,3.4735389716f,3.4740026081f,3.4744662403f,3.4749298682f, + 3.4753934919f,3.4758571114f,3.4763207266f,3.4767843376f,3.4772479444f,3.4777115469f, + 3.4781751452f,3.4786387393f,3.4791023292f,3.4795659148f,3.4800294963f,3.4804930735f, + 3.4809566465f,3.4814202153f,3.4818837799f,3.4823473403f,3.4828108965f,3.4832744485f, + 3.4837379963f,3.4842015399f,3.4846650793f,3.4851286145f,3.4855921455f,3.4860556723f, + 3.4865191950f,3.4869827135f,3.4874462277f,3.4879097379f,3.4883732438f,3.4888367456f, + 3.4893002432f,3.4897637366f,3.4902272259f,3.4906907110f,3.4911541919f,3.4916176687f, + 3.4920811413f,3.4925446098f,3.4930080741f,3.4934715343f,3.4939349903f,3.4943984422f, + 3.4948618899f,3.4953253335f,3.4957887730f,3.4962522083f,3.4967156395f,3.4971790666f, + 3.4976424895f,3.4981059083f,3.4985693230f,3.4990327335f,3.4994961400f,3.4999595423f, + 3.5004229405f,3.5008863346f,3.5013497246f,3.5018131104f,3.5022764922f,3.5027398699f, + 3.5032032434f,3.5036666129f,3.5041299782f,3.5045933395f,3.5050566967f,3.5055200498f, + 3.5059833988f,3.5064467437f,3.5069100845f,3.5073734213f,3.5078367539f,3.5083000825f, + 3.5087634070f,3.5092267275f,3.5096900439f,3.5101533562f,3.5106166644f,3.5110799686f, + 3.5115432687f,3.5120065648f,3.5124698568f,3.5129331447f,3.5133964286f,3.5138597085f, + 3.5143229842f,3.5147862560f,3.5152495237f,3.5157127874f,3.5161760470f,3.5166393026f, + 3.5171025542f,3.5175658017f,3.5180290452f,3.5184922846f,3.5189555201f,3.5194187515f, + 3.5198819789f,3.5203452023f,3.5208084217f,3.5212716370f,3.5217348483f,3.5221980557f, + 3.5226612590f,3.5231244583f,3.5235876536f,3.5240508449f,3.5245140323f,3.5249772156f, + 3.5254403949f,3.5259035702f,3.5263667416f,3.5268299089f,3.5272930723f,3.5277562317f, + 3.5282193871f,3.5286825385f,3.5291456860f,3.5296088294f,3.5300719689f,3.5305351045f, + 3.5309982360f,3.5314613636f,3.5319244873f,3.5323876069f,3.5328507227f,3.5333138344f, + 3.5337769422f,3.5342400461f,3.5347031460f,3.5351662419f,3.5356293339f,3.5360924220f, + 3.5365555061f,3.5370185863f,3.5374816626f,3.5379447349f,3.5384078032f,3.5388708677f, + 3.5393339282f,3.5397969848f,3.5402600375f,3.5407230862f,3.5411861311f,3.5416491720f, + 3.5421122090f,3.5425752421f,3.5430382712f,3.5435012965f,3.5439643179f,3.5444273353f, + 3.5448903489f,3.5453533585f,3.5458163643f,3.5462793661f,3.5467423641f,3.5472053582f, + 3.5476683484f,3.5481313347f,3.5485943171f,3.5490572956f,3.5495202702f,3.5499832410f, + 3.5504462079f,3.5509091709f,3.5513721301f,3.5518350854f,3.5522980368f,3.5527609843f, + 3.5532239280f,3.5536868678f,3.5541498038f,3.5546127359f,3.5550756642f,3.5555385886f, + 3.5560015091f,3.5564644258f,3.5569273387f,3.5573902477f,3.5578531529f,3.5583160542f, + 3.5587789517f,3.5592418453f,3.5597047352f,3.5601676212f,3.5606305033f,3.5610933817f, + 3.5615562562f,3.5620191269f,3.5624819937f,3.5629448568f,3.5634077160f,3.5638705714f, + 3.5643334231f,3.5647962709f,3.5652591149f,3.5657219550f,3.5661847914f,3.5666476240f, + 3.5671104528f,3.5675732778f,3.5680360990f,3.5684989164f,3.5689617300f,3.5694245398f, + 3.5698873459f,3.5703501481f,3.5708129466f,3.5712757413f,3.5717385322f,3.5722013193f, + 3.5726641027f,3.5731268823f,3.5735896581f,3.5740524301f,3.5745151984f,3.5749779630f, + 3.5754407237f,3.5759034807f,3.5763662340f,3.5768289835f,3.5772917292f,3.5777544712f, + 3.5782172094f,3.5786799439f,3.5791426747f,3.5796054017f,3.5800681250f,3.5805308445f, + 3.5809935603f,3.5814562724f,3.5819189807f,3.5823816853f,3.5828443862f,3.5833070833f, + 3.5837697768f,3.5842324665f,3.5846951525f,3.5851578347f,3.5856205133f,3.5860831881f, + 3.5865458593f,3.5870085267f,3.5874711904f,3.5879338504f,3.5883965067f,3.5888591593f, + 3.5893218083f,3.5897844535f,3.5902470950f,3.5907097328f,3.5911723670f,3.5916349974f, + 3.5920976242f,3.5925602473f,3.5930228667f,3.5934854824f,3.5939480944f,3.5944107028f, + 3.5948733075f,3.5953359085f,3.5957985059f,3.5962610996f,3.5967236896f,3.5971862760f, + 3.5976488587f,3.5981114377f,3.5985740131f,3.5990365848f,3.5994991529f,3.5999617173f, + 3.6004242781f,3.6008868352f,3.6013493887f,3.6018119386f,3.6022744848f,3.6027370273f, + 3.6031995663f,3.6036621016f,3.6041246332f,3.6045871613f,3.6050496857f,3.6055122065f, + 3.6059747236f,3.6064372371f,3.6068997471f,3.6073622534f,3.6078247560f,3.6082872551f, + 3.6087497506f,3.6092122424f,3.6096747307f,3.6101372153f,3.6105996963f,3.6110621737f, + 3.6115246476f,3.6119871178f,3.6124495844f,3.6129120475f,3.6133745069f,3.6138369628f, + 3.6142994151f,3.6147618638f,3.6152243089f,3.6156867504f,3.6161491883f,3.6166116227f, + 3.6170740535f,3.6175364807f,3.6179989044f,3.6184613245f,3.6189237410f,3.6193861539f, + 3.6198485633f,3.6203109691f,3.6207733714f,3.6212357701f,3.6216981653f,3.6221605569f, + 3.6226229449f,3.6230853294f,3.6235477104f,3.6240100878f,3.6244724617f,3.6249348320f, + 3.6253971988f,3.6258595620f,3.6263219217f,3.6267842779f,3.6272466306f,3.6277089797f, + 3.6281713253f,3.6286336674f,3.6290960059f,3.6295583410f,3.6300206725f,3.6304830005f, + 3.6309453249f,3.6314076459f,3.6318699634f,3.6323322773f,3.6327945878f,3.6332568947f, + 3.6337191981f,3.6341814981f,3.6346437945f,3.6351060874f,3.6355683769f,3.6360306628f, + 3.6364929453f,3.6369552243f,3.6374174997f,3.6378797717f,3.6383420402f,3.6388043053f, + 3.6392665668f,3.6397288249f,3.6401910795f,3.6406533306f,3.6411155783f,3.6415778225f, + 3.6420400632f,3.6425023005f,3.6429645343f,3.6434267646f,3.6438889915f,3.6443512149f, + 3.6448134349f,3.6452756514f,3.6457378644f,3.6462000740f,3.6466622802f,3.6471244829f, + 3.6475866822f,3.6480488780f,3.6485110704f,3.6489732594f,3.6494354449f,3.6498976270f, + 3.6503598056f,3.6508219808f,3.6512841526f,3.6517463210f,3.6522084859f,3.6526706475f, + 3.6531328056f,3.6535949603f,3.6540571115f,3.6545192594f,3.6549814038f,3.6554435449f, + 3.6559056825f,3.6563678167f,3.6568299475f,3.6572920749f,3.6577541989f,3.6582163196f, + 3.6586784368f,3.6591405506f,3.6596026610f,3.6600647681f,3.6605268717f,3.6609889720f, + 3.6614510689f,3.6619131624f,3.6623752525f,3.6628373392f,3.6632994226f,3.6637615026f, + 3.6642235792f,3.6646856524f,3.6651477223f,3.6656097888f,3.6660718519f,3.6665339117f, + 3.6669959681f,3.6674580212f,3.6679200708f,3.6683821172f,3.6688441602f,3.6693061998f, + 3.6697682361f,3.6702302690f,3.6706922986f,3.6711543249f,3.6716163478f,3.6720783673f, + 3.6725403835f,3.6730023964f,3.6734644060f,3.6739264122f,3.6743884151f,3.6748504146f, + 3.6753124109f,3.6757744038f,3.6762363934f,3.6766983796f,3.6771603626f,3.6776223422f, + 3.6780843185f,3.6785462915f,3.6790082612f,3.6794702276f,3.6799321906f,3.6803941504f, + 3.6808561068f,3.6813180600f,3.6817800098f,3.6822419564f,3.6827038997f,3.6831658396f, + 3.6836277763f,3.6840897097f,3.6845516398f,3.6850135666f,3.6854754901f,3.6859374103f, + 3.6863993273f,3.6868612410f,3.6873231514f,3.6877850585f,3.6882469623f,3.6887088629f, + 3.6891707602f,3.6896326543f,3.6900945450f,3.6905564325f,3.6910183168f,3.6914801978f, + 3.6919420755f,3.6924039500f,3.6928658212f,3.6933276891f,3.6937895539f,3.6942514153f, + 3.6947132735f,3.6951751285f,3.6956369802f,3.6960988287f,3.6965606739f,3.6970225160f, + 3.6974843547f,3.6979461903f,3.6984080226f,3.6988698516f,3.6993316775f,3.6997935001f, + 3.7002553195f,3.7007171357f,3.7011789486f,3.7016407583f,3.7021025648f,3.7025643681f, + 3.7030261682f,3.7034879651f,3.7039497588f,3.7044115492f,3.7048733365f,3.7053351205f, + 3.7057969013f,3.7062586790f,3.7067204534f,3.7071822247f,3.7076439927f,3.7081057576f, + 3.7085675192f,3.7090292777f,3.7094910330f,3.7099527851f,3.7104145340f,3.7108762797f, + 3.7113380223f,3.7117997617f,3.7122614979f,3.7127232309f,3.7131849607f,3.7136466874f, + 3.7141084109f,3.7145701313f,3.7150318485f,3.7154935625f,3.7159552733f,3.7164169810f, + 3.7168786855f,3.7173403869f,3.7178020852f,3.7182637802f,3.7187254721f,3.7191871609f, + 3.7196488465f,3.7201105290f,3.7205722084f,3.7210338846f,3.7214955576f,3.7219572275f, + 3.7224188943f,3.7228805580f,3.7233422185f,3.7238038759f,3.7242655301f,3.7247271812f, + 3.7251888292f,3.7256504741f,3.7261121159f,3.7265737545f,3.7270353900f,3.7274970225f, + 3.7279586517f,3.7284202779f,3.7288819010f,3.7293435210f,3.7298051378f,3.7302667516f, + 3.7307283622f,3.7311899697f,3.7316515742f,3.7321131755f,3.7325747738f,3.7330363689f, + 3.7334979610f,3.7339595500f,3.7344211359f,3.7348827187f,3.7353442984f,3.7358058750f, + 3.7362674486f,3.7367290190f,3.7371905864f,3.7376521507f,3.7381137120f,3.7385752701f, + 3.7390368252f,3.7394983773f,3.7399599262f,3.7404214721f,3.7408830149f,3.7413445547f, + 3.7418060914f,3.7422676251f,3.7427291557f,3.7431906832f,3.7436522077f,3.7441137291f, + 3.7445752475f,3.7450367629f,3.7454982752f,3.7459597844f,3.7464212906f,3.7468827938f, + 3.7473442939f,3.7478057910f,3.7482672851f,3.7487287761f,3.7491902641f,3.7496517491f, + 3.7501132310f,3.7505747100f,3.7510361858f,3.7514976587f,3.7519591286f,3.7524205954f, + 3.7528820592f,3.7533435200f,3.7538049778f,3.7542664326f,3.7547278843f,3.7551893331f, + 3.7556507788f,3.7561122216f,3.7565736613f,3.7570350981f,3.7574965318f,3.7579579625f, + 3.7584193903f,3.7588808150f,3.7593422368f,3.7598036556f,3.7602650714f,3.7607264842f, + 3.7611878940f,3.7616493008f,3.7621107046f,3.7625721055f,3.7630335034f,3.7634948983f, + 3.7639562903f,3.7644176792f,3.7648790652f,3.7653404482f,3.7658018283f,3.7662632054f, + 3.7667245795f,3.7671859507f,3.7676473189f,3.7681086842f,3.7685700465f,3.7690314058f, + 3.7694927622f,3.7699541156f,3.7704154661f,3.7708768136f,3.7713381582f,3.7717994999f, + 3.7722608386f,3.7727221743f,3.7731835071f,3.7736448370f,3.7741061640f,3.7745674880f, + 3.7750288091f,3.7754901272f,3.7759514424f,3.7764127547f,3.7768740641f,3.7773353705f, + 3.7777966741f,3.7782579747f,3.7787192724f,3.7791805671f,3.7796418590f,3.7801031479f, + 3.7805644339f,3.7810257171f,3.7814869973f,3.7819482746f,3.7824095490f,3.7828708205f, + 3.7833320891f,3.7837933548f,3.7842546176f,3.7847158775f,3.7851771345f,3.7856383886f, + 3.7860996398f,3.7865608882f,3.7870221336f,3.7874833762f,3.7879446159f,3.7884058527f, + 3.7888670866f,3.7893283176f,3.7897895458f,3.7902507711f,3.7907119935f,3.7911732130f, + 3.7916344297f,3.7920956435f,3.7925568544f,3.7930180625f,3.7934792677f,3.7939404701f, + 3.7944016696f,3.7948628662f,3.7953240600f,3.7957852509f,3.7962464390f,3.7967076242f, + 3.7971688065f,3.7976299861f,3.7980911627f,3.7985523366f,3.7990135075f,3.7994746757f, + 3.7999358410f,3.8003970035f,3.8008581631f,3.8013193199f,3.8017804739f,3.8022416250f, + 3.8027027733f,3.8031639188f,3.8036250614f,3.8040862013f,3.8045473383f,3.8050084725f, + 3.8054696038f,3.8059307324f,3.8063918581f,3.8068529810f,3.8073141011f,3.8077752184f, + 3.8082363329f,3.8086974446f,3.8091585535f,3.8096196596f,3.8100807628f,3.8105418633f, + 3.8110029610f,3.8114640559f,3.8119251479f,3.8123862372f,3.8128473237f,3.8133084074f, + 3.8137694884f,3.8142305665f,3.8146916418f,3.8151527144f,3.8156137842f,3.8160748512f, + 3.8165359154f,3.8169969768f,3.8174580355f,3.8179190914f,3.8183801445f,3.8188411949f, + 3.8193022425f,3.8197632873f,3.8202243294f,3.8206853687f,3.8211464052f,3.8216074390f, + 3.8220684700f,3.8225294982f,3.8229905237f,3.8234515465f,3.8239125665f,3.8243735837f, + 3.8248345982f,3.8252956100f,3.8257566190f,3.8262176253f,3.8266786288f,3.8271396296f, + 3.8276006276f,3.8280616229f,3.8285226155f,3.8289836053f,3.8294445925f,3.8299055768f, + 3.8303665585f,3.8308275374f,3.8312885136f,3.8317494871f,3.8322104578f,3.8326714259f, + 3.8331323912f,3.8335933538f,3.8340543137f,3.8345152708f,3.8349762253f,3.8354371771f, + 3.8358981261f,3.8363590724f,3.8368200161f,3.8372809570f,3.8377418952f,3.8382028307f, + 3.8386637636f,3.8391246937f,3.8395856211f,3.8400465459f,3.8405074679f,3.8409683873f, + 3.8414293039f,3.8418902179f,3.8423511292f,3.8428120378f,3.8432729438f,3.8437338470f, + 3.8441947476f,3.8446556455f,3.8451165407f,3.8455774332f,3.8460383231f,3.8464992103f, + 3.8469600948f,3.8474209767f,3.8478818559f,3.8483427324f,3.8488036063f,3.8492644775f, + 3.8497253461f,3.8501862120f,3.8506470752f,3.8511079358f,3.8515687937f,3.8520296490f, + 3.8524905016f,3.8529513516f,3.8534121990f,3.8538730437f,3.8543338857f,3.8547947251f, + 3.8552555619f,3.8557163960f,3.8561772275f,3.8566380564f,3.8570988826f,3.8575597062f, + 3.8580205272f,3.8584813455f,3.8589421612f,3.8594029743f,3.8598637847f,3.8603245926f, + 3.8607853978f,3.8612462004f,3.8617070004f,3.8621677977f,3.8626285925f,3.8630893846f, + 3.8635501742f,3.8640109611f,3.8644717454f,3.8649325271f,3.8653933062f,3.8658540827f, + 3.8663148566f,3.8667756279f,3.8672363966f,3.8676971627f,3.8681579262f,3.8686186872f, + 3.8690794455f,3.8695402012f,3.8700009544f,3.8704617049f,3.8709224529f,3.8713831983f, + 3.8718439411f,3.8723046814f,3.8727654190f,3.8732261541f,3.8736868866f,3.8741476165f, + 3.8746083439f,3.8750690687f,3.8755297909f,3.8759905105f,3.8764512276f,3.8769119421f, + 3.8773726541f,3.8778333635f,3.8782940703f,3.8787547746f,3.8792154763f,3.8796761755f, + 3.8801368721f,3.8805975661f,3.8810582576f,3.8815189466f,3.8819796330f,3.8824403169f, + 3.8829009982f,3.8833616770f,3.8838223532f,3.8842830270f,3.8847436981f,3.8852043668f, + 3.8856650329f,3.8861256964f,3.8865863575f,3.8870470160f,3.8875076719f,3.8879683254f, + 3.8884289763f,3.8888896247f,3.8893502706f,3.8898109140f,3.8902715548f,3.8907321931f, + 3.8911928289f,3.8916534622f,3.8921140930f,3.8925747213f,3.8930353471f,3.8934959703f, + 3.8939565911f,3.8944172093f,3.8948778251f,3.8953384383f,3.8957990490f,3.8962596573f, + 3.8967202630f,3.8971808663f,3.8976414670f,3.8981020653f,3.8985626611f,3.8990232544f, + 3.8994838452f,3.8999444335f,3.9004050193f,3.9008656027f,3.9013261836f,3.9017867619f, + 3.9022473379f,3.9027079113f,3.9031684823f,3.9036290507f,3.9040896168f,3.9045501803f, + 3.9050107414f,3.9054713000f,3.9059318561f,3.9063924098f,3.9068529610f,3.9073135098f, + 3.9077740561f,3.9082345999f,3.9086951413f,3.9091556803f,3.9096162167f,3.9100767508f, + 3.9105372823f,3.9109978115f,3.9114583382f,3.9119188624f,3.9123793842f,3.9128399035f, + 3.9133004204f,3.9137609349f,3.9142214469f,3.9146819565f,3.9151424637f,3.9156029684f, + 3.9160634707f,3.9165239706f,3.9169844680f,3.9174449630f,3.9179054556f,3.9183659458f, + 3.9188264335f,3.9192869189f,3.9197474018f,3.9202078822f,3.9206683603f,3.9211288360f, + 3.9215893092f,3.9220497800f,3.9225102484f,3.9229707145f,3.9234311781f,3.9238916393f, + 3.9243520980f,3.9248125544f,3.9252730084f,3.9257334600f,3.9261939092f,3.9266543560f, + 3.9271148004f,3.9275752424f,3.9280356820f,3.9284961193f,3.9289565541f,3.9294169865f, + 3.9298774166f,3.9303378443f,3.9307982696f,3.9312586925f,3.9317191130f,3.9321795312f, + 3.9326399470f,3.9331003604f,3.9335607714f,3.9340211801f,3.9344815864f,3.9349419903f, + 3.9354023918f,3.9358627910f,3.9363231879f,3.9367835823f,3.9372439744f,3.9377043642f, + 3.9381647515f,3.9386251366f,3.9390855192f,3.9395458995f,3.9400062775f,3.9404666531f, + 3.9409270264f,3.9413873973f,3.9418477659f,3.9423081321f,3.9427684960f,3.9432288575f, + 3.9436892167f,3.9441495735f,3.9446099281f,3.9450702802f,3.9455306301f,3.9459909776f, + 3.9464513228f,3.9469116656f,3.9473720062f,3.9478323444f,3.9482926802f,3.9487530138f, + 3.9492133450f,3.9496736739f,3.9501340005f,3.9505943248f,3.9510546467f,3.9515149664f, + 3.9519752837f,3.9524355987f,3.9528959114f,3.9533562218f,3.9538165299f,3.9542768356f, + 3.9547371391f,3.9551974403f,3.9556577392f,3.9561180357f,3.9565783300f,3.9570386220f, + 3.9574989116f,3.9579591990f,3.9584194841f,3.9588797669f,3.9593400474f,3.9598003256f, + 3.9602606016f,3.9607208752f,3.9611811466f,3.9616414157f,3.9621016825f,3.9625619470f, + 3.9630222092f,3.9634824692f,3.9639427269f,3.9644029823f,3.9648632354f,3.9653234863f, + 3.9657837349f,3.9662439812f,3.9667042253f,3.9671644671f,3.9676247067f,3.9680849439f, + 3.9685451790f,3.9690054117f,3.9694656422f,3.9699258705f,3.9703860965f,3.9708463202f, + 3.9713065417f,3.9717667609f,3.9722269779f,3.9726871926f,3.9731474051f,3.9736076154f, + 3.9740678234f,3.9745280292f,3.9749882327f,3.9754484340f,3.9759086330f,3.9763688298f, + 3.9768290244f,3.9772892168f,3.9777494069f,3.9782095948f,3.9786697804f,3.9791299639f, + 3.9795901451f,3.9800503241f,3.9805105008f,3.9809706754f,3.9814308477f,3.9818910178f, + 3.9823511857f,3.9828113513f,3.9832715148f,3.9837316760f,3.9841918351f,3.9846519919f, + 3.9851121465f,3.9855722989f,3.9860324491f,3.9864925971f,3.9869527429f,3.9874128865f, + 3.9878730279f,3.9883331671f,3.9887933041f,3.9892534389f,3.9897135715f,3.9901737020f, + 3.9906338302f,3.9910939562f,3.9915540801f,3.9920142017f,3.9924743212f,3.9929344385f, + 3.9933945536f,3.9938546666f,3.9943147773f,3.9947748859f,3.9952349923f,3.9956950965f, + 3.9961551986f,3.9966152985f,3.9970753962f,3.9975354917f,3.9979955851f,3.9984556763f, + 3.9989157654f,3.9993758522f,3.9998359370f,4.0002960195f,4.0007560999f,4.0012161781f, + 4.0016762542f,4.0021363282f,4.0025963999f,4.0030564696f,4.0035165370f,4.0039766024f, + 4.0044366655f,4.0048967266f,4.0053567854f,4.0058168422f,4.0062768968f,4.0067369492f, + 4.0071969996f,4.0076570477f,4.0081170938f,4.0085771377f,4.0090371795f,4.0094972191f, + 4.0099572566f,4.0104172920f,4.0108773252f,4.0113373564f,4.0117973854f,4.0122574123f, + 4.0127174370f,4.0131774596f,4.0136374802f,4.0140974986f,4.0145575148f,4.0150175290f, + 4.0154775411f,4.0159375510f,4.0163975588f,4.0168575645f,4.0173175681f,4.0177775696f, + 4.0182375690f,4.0186975663f,4.0191575615f,4.0196175546f,4.0200775456f,4.0205375345f, + 4.0209975213f,4.0214575060f,4.0219174886f,4.0223774691f,4.0228374475f,4.0232974239f, + 4.0237573981f,4.0242173703f,4.0246773403f,4.0251373083f,4.0255972742f,4.0260572380f, + 4.0265171998f,4.0269771594f,4.0274371170f,4.0278970725f,4.0283570259f,4.0288169773f, + 4.0292769266f,4.0297368738f,4.0301968189f,4.0306567620f,4.0311167030f,4.0315766420f, + 4.0320365789f,4.0324965137f,4.0329564464f,4.0334163771f,4.0338763058f,4.0343362324f, + 4.0347961569f,4.0352560794f,4.0357159998f,4.0361759182f,4.0366358345f,4.0370957488f, + 4.0375556610f,4.0380155712f,4.0384754793f,4.0389353854f,4.0393952895f,4.0398551915f, + 4.0403150915f,4.0407749894f,4.0412348853f,4.0416947792f,4.0421546710f,4.0426145608f, + 4.0430744486f,4.0435343343f,4.0439942180f,4.0444540997f,4.0449139794f,4.0453738570f, + 4.0458337327f,4.0462936062f,4.0467534778f,4.0472133474f,4.0476732149f,4.0481330805f, + 4.0485929440f,4.0490528055f,4.0495126650f,4.0499725224f,4.0504323779f,4.0508922314f, + 4.0513520828f,4.0518119323f,4.0522717797f,4.0527316252f,4.0531914686f,4.0536513101f, + 4.0541111495f,4.0545709870f,4.0550308225f,4.0554906559f,4.0559504874f,4.0564103169f, + 4.0568701444f,4.0573299699f,4.0577897934f,4.0582496149f,4.0587094345f,4.0591692521f, + 4.0596290676f,4.0600888812f,4.0605486929f,4.0610085025f,4.0614683102f,4.0619281159f, + 4.0623879196f,4.0628477214f,4.0633075212f,4.0637673190f,4.0642271148f,4.0646869087f, + 4.0651467006f,4.0656064906f,4.0660662786f,4.0665260646f,4.0669858487f,4.0674456308f, + 4.0679054109f,4.0683651891f,4.0688249654f,4.0692847397f,4.0697445120f,4.0702042824f, + 4.0706640508f,4.0711238173f,4.0715835819f,4.0720433445f,4.0725031051f,4.0729628639f, + 4.0734226206f,4.0738823755f,4.0743421284f,4.0748018793f,4.0752616283f,4.0757213754f, + 4.0761811206f,4.0766408638f,4.0771006051f,4.0775603445f,4.0780200819f,4.0784798174f, + 4.0789395510f,4.0793992827f,4.0798590124f,4.0803187402f,4.0807784661f,4.0812381901f, + 4.0816979122f,4.0821576323f,4.0826173506f,4.0830770669f,4.0835367813f,4.0839964938f, + 4.0844562044f,4.0849159131f,4.0853756199f,4.0858353248f,4.0862950277f,4.0867547288f, + 4.0872144280f,4.0876741252f,4.0881338206f,4.0885935141f,4.0890532057f,4.0895128953f, + 4.0899725831f,4.0904322690f,4.0908919530f,4.0913516351f,4.0918113154f,4.0922709937f, + 4.0927306702f,4.0931903448f,4.0936500175f,4.0941096883f,4.0945693572f,4.0950290242f, + 4.0954886894f,4.0959483527f,4.0964080142f,4.0968676737f,4.0973273314f,4.0977869872f, + 4.0982466411f,4.0987062932f,4.0991659434f,4.0996255918f,4.1000852382f,4.1005448828f, + 4.1010045256f,4.1014641665f,4.1019238055f,4.1023834427f,4.1028430780f,4.1033027115f, + 4.1037623431f,4.1042219728f,4.1046816008f,4.1051412268f,4.1056008510f,4.1060604734f, + 4.1065200939f,4.1069797126f,4.1074393294f,4.1078989444f,4.1083585575f,4.1088181688f, + 4.1092777783f,4.1097373859f,4.1101969917f,4.1106565957f,4.1111161978f,4.1115757981f, + 4.1120353966f,4.1124949932f,4.1129545880f,4.1134141810f,4.1138737722f,4.1143333615f, + 4.1147929490f,4.1152525347f,4.1157121186f,4.1161717007f,4.1166312809f,4.1170908593f, + 4.1175504359f,4.1180100107f,4.1184695837f,4.1189291549f,4.1193887242f,4.1198482918f, + 4.1203078575f,4.1207674215f,4.1212269836f,4.1216865439f,4.1221461025f,4.1226056592f, + 4.1230652141f,4.1235247673f,4.1239843186f,4.1244438681f,4.1249034159f,4.1253629618f, + 4.1258225060f,4.1262820484f,4.1267415890f,4.1272011277f,4.1276606648f,4.1281202000f, + 4.1285797334f,4.1290392651f,4.1294987949f,4.1299583230f,4.1304178494f,4.1308773739f, + 4.1313368967f,4.1317964177f,4.1322559369f,4.1327154543f,4.1331749700f,4.1336344839f, + 4.1340939960f,4.1345535064f,4.1350130150f,4.1354725218f,4.1359320269f,4.1363915302f, + 4.1368510317f,4.1373105315f,4.1377700296f,4.1382295258f,4.1386890203f,4.1391485131f, + 4.1396080041f,4.1400674934f,4.1405269809f,4.1409864666f,4.1414459506f,4.1419054329f, + 4.1423649134f,4.1428243922f,4.1432838692f,4.1437433445f,4.1442028180f,4.1446622899f, + 4.1451217599f,4.1455812283f,4.1460406948f,4.1465001597f,4.1469596228f,4.1474190842f, + 4.1478785439f,4.1483380018f,4.1487974580f,4.1492569125f,4.1497163653f,4.1501758163f, + 4.1506352656f,4.1510947132f,4.1515541591f,4.1520136032f,4.1524730456f,4.1529324863f, + 4.1533919253f,4.1538513626f,4.1543107982f,4.1547702320f,4.1552296642f,4.1556890946f, + 4.1561485234f,4.1566079504f,4.1570673757f,4.1575267993f,4.1579862212f,4.1584456414f, + 4.1589050599f,4.1593644767f,4.1598238918f,4.1602833053f,4.1607427170f,4.1612021270f, + 4.1616615353f,4.1621209420f,4.1625803469f,4.1630397502f,4.1634991517f,4.1639585516f, + 4.1644179498f,4.1648773463f,4.1653367412f,4.1657961343f,4.1662555258f,4.1667149156f, + 4.1671743037f,4.1676336901f,4.1680930749f,4.1685524579f,4.1690118393f,4.1694712191f, + 4.1699305971f,4.1703899735f,4.1708493483f,4.1713087213f,4.1717680927f,4.1722274625f, + 4.1726868305f,4.1731461969f,4.1736055617f,4.1740649248f,4.1745242862f,4.1749836459f, + 4.1754430041f,4.1759023605f,4.1763617153f,4.1768210685f,4.1772804200f,4.1777397698f, + 4.1781991180f,4.1786584646f,4.1791178095f,4.1795771528f,4.1800364944f,4.1804958344f, + 4.1809551727f,4.1814145094f,4.1818738444f,4.1823331779f,4.1827925096f,4.1832518398f, + 4.1837111683f,4.1841704952f,4.1846298204f,4.1850891441f,4.1855484660f,4.1860077864f, + 4.1864671051f,4.1869264223f,4.1873857377f,4.1878450516f,4.1883043639f,4.1887636745f, + 4.1892229835f,4.1896822909f,4.1901415966f,4.1906009008f,4.1910602033f,4.1915195042f, + 4.1919788036f,4.1924381013f,4.1928973973f,4.1933566918f,4.1938159847f,4.1942752760f, + 4.1947345656f,4.1951938537f,4.1956531402f,4.1961124250f,4.1965717083f,4.1970309899f, + 4.1974902700f,4.1979495485f,4.1984088253f,4.1988681006f,4.1993273743f,4.1997866464f, + 4.2002459169f,4.2007051858f,4.2011644531f,4.2016237189f,4.2020829830f,4.2025422456f, + 4.2030015066f,4.2034607660f,4.2039200238f,4.2043792800f,4.2048385347f,4.2052977878f, + 4.2057570393f,4.2062162892f,4.2066755376f,4.2071347844f,4.2075940296f,4.2080532733f, + 4.2085125153f,4.2089717559f,4.2094309948f,4.2098902322f,4.2103494680f,4.2108087023f, + 4.2112679350f,4.2117271661f,4.2121863957f,4.2126456237f,4.2131048502f,4.2135640751f, + 4.2140232984f,4.2144825202f,4.2149417405f,4.2154009592f,4.2158601763f,4.2163193919f, + 4.2167786059f,4.2172378185f,4.2176970294f,4.2181562388f,4.2186154467f,4.2190746530f, + 4.2195338578f,4.2199930611f,4.2204522628f,4.2209114630f,4.2213706616f,4.2218298587f, + 4.2222890543f,4.2227482483f,4.2232074409f,4.2236666318f,4.2241258213f,4.2245850092f, + 4.2250441956f,4.2255033805f,4.2259625639f,4.2264217457f,4.2268809260f,4.2273401048f, + 4.2277992821f,4.2282584578f,4.2287176321f,4.2291768048f,4.2296359760f,4.2300951457f, + 4.2305543139f,4.2310134806f,4.2314726457f,4.2319318094f,4.2323909715f,4.2328501322f, + 4.2333092913f,4.2337684489f,4.2342276051f,4.2346867597f,4.2351459128f,4.2356050645f, + 4.2360642146f,4.2365233632f,4.2369825104f,4.2374416560f,4.2379008002f,4.2383599429f, + 4.2388190840f,4.2392782237f,4.2397373619f,4.2401964986f,4.2406556338f,4.2411147676f, + 4.2415738998f,4.2420330306f,4.2424921599f,4.2429512877f,4.2434104140f,4.2438695389f, + 4.2443286623f,4.2447877842f,4.2452469046f,4.2457060235f,4.2461651410f,4.2466242570f, + 4.2470833716f,4.2475424846f,4.2480015962f,4.2484607064f,4.2489198150f,4.2493789223f, + 4.2498380280f,4.2502971323f,4.2507562351f,4.2512153365f,4.2516744364f,4.2521335348f, + 4.2525926318f,4.2530517274f,4.2535108214f,4.2539699141f,4.2544290053f,4.2548880950f, + 4.2553471833f,4.2558062701f,4.2562653555f,4.2567244394f,4.2571835219f,4.2576426030f, + 4.2581016826f,4.2585607608f,4.2590198375f,4.2594789128f,4.2599379867f,4.2603970591f, + 4.2608561301f,4.2613151996f,4.2617742677f,4.2622333344f,4.2626923997f,4.2631514635f, + 4.2636105259f,4.2640695869f,4.2645286464f,4.2649877045f,4.2654467612f,4.2659058165f, + 4.2663648703f,4.2668239228f,4.2672829738f,4.2677420234f,4.2682010716f,4.2686601183f, + 4.2691191637f,4.2695782076f,4.2700372501f,4.2704962912f,4.2709553309f,4.2714143692f, + 4.2718734061f,4.2723324416f,4.2727914756f,4.2732505083f,4.2737095396f,4.2741685694f, + 4.2746275979f,4.2750866250f,4.2755456506f,4.2760046749f,4.2764636978f,4.2769227192f, + 4.2773817393f,4.2778407580f,4.2782997753f,4.2787587912f,4.2792178057f,4.2796768188f, + 4.2801358306f,4.2805948409f,4.2810538499f,4.2815128575f,4.2819718637f,4.2824308685f, + 4.2828898719f,4.2833488740f,4.2838078746f,4.2842668739f,4.2847258719f,4.2851848684f, + 4.2856438636f,4.2861028574f,4.2865618498f,4.2870208409f,4.2874798306f,4.2879388189f, + 4.2883978059f,4.2888567914f,4.2893157757f,4.2897747585f,4.2902337400f,4.2906927202f, + 4.2911516989f,4.2916106764f,4.2920696524f,4.2925286271f,4.2929876005f,4.2934465725f, + 4.2939055431f,4.2943645124f,4.2948234803f,4.2952824469f,4.2957414121f,4.2962003760f, + 4.2966593386f,4.2971182998f,4.2975772596f,4.2980362181f,4.2984951753f,4.2989541311f, + 4.2994130856f,4.2998720387f,4.3003309905f,4.3007899410f,4.3012488901f,4.3017078379f, + 4.3021667844f,4.3026257295f,4.3030846733f,4.3035436158f,4.3040025569f,4.3044614967f, + 4.3049204352f,4.3053793723f,4.3058383082f,4.3062972427f,4.3067561759f,4.3072151077f, + 4.3076740383f,4.3081329675f,4.3085918954f,4.3090508220f,4.3095097473f,4.3099686712f, + 4.3104275939f,4.3108865152f,4.3113454352f,4.3118043539f,4.3122632713f,4.3127221874f, + 4.3131811022f,4.3136400157f,4.3140989278f,4.3145578387f,4.3150167483f,4.3154756565f, + 4.3159345635f,4.3163934692f,4.3168523735f,4.3173112766f,4.3177701784f,4.3182290788f, + 4.3186879780f,4.3191468759f,4.3196057725f,4.3200646678f,4.3205235618f,4.3209824546f, + 4.3214413460f,4.3219002362f,4.3223591250f,4.3228180126f,4.3232768989f,4.3237357839f, + 4.3241946677f,4.3246535501f,4.3251124313f,4.3255713112f,4.3260301898f,4.3264890672f, + 4.3269479433f,4.3274068181f,4.3278656916f,4.3283245639f,4.3287834348f,4.3292423046f, + 4.3297011730f,4.3301600402f,4.3306189061f,4.3310777708f,4.3315366342f,4.3319954963f, + 4.3324543572f,4.3329132168f,4.3333720751f,4.3338309322f,4.3342897880f,4.3347486426f, + 4.3352074959f,4.3356663480f,4.3361251988f,4.3365840484f,4.3370428967f,4.3375017438f, + 4.3379605896f,4.3384194342f,4.3388782775f,4.3393371195f,4.3397959604f,4.3402548000f, + 4.3407136383f,4.3411724754f,4.3416313113f,4.3420901459f,4.3425489793f,4.3430078115f, + 4.3434666424f,4.3439254721f,4.3443843005f,4.3448431277f,4.3453019537f,4.3457607785f, + 4.3462196020f,4.3466784243f,4.3471372454f,4.3475960652f,4.3480548838f,4.3485137012f, + 4.3489725174f,4.3494313324f,4.3498901461f,4.3503489586f,4.3508077699f,4.3512665800f, + 4.3517253888f,4.3521841965f,4.3526430029f,4.3531018081f,4.3535606121f,4.3540194149f, + 4.3544782165f,4.3549370169f,4.3553958160f,4.3558546140f,4.3563134108f,4.3567722063f, + 4.3572310007f,4.3576897938f,4.3581485858f,4.3586073765f,4.3590661660f,4.3595249544f, + 4.3599837415f,4.3604425275f,4.3609013122f,4.3613600958f,4.3618188782f,4.3622776594f, + 4.3627364393f,4.3631952181f,4.3636539957f,4.3641127722f,4.3645715474f,4.3650303215f, + 4.3654890943f,4.3659478660f,4.3664066365f,4.3668654058f,4.3673241739f,4.3677829409f, + 4.3682417067f,4.3687004713f,4.3691592347f,4.3696179970f,4.3700767580f,4.3705355179f, + 4.3709942767f,4.3714530342f,4.3719117906f,4.3723705459f,4.3728292999f,4.3732880528f, + 4.3737468045f,4.3742055551f,4.3746643045f,4.3751230527f,4.3755817998f,4.3760405457f, + 4.3764992904f,4.3769580340f,4.3774167765f,4.3778755177f,4.3783342579f,4.3787929968f, + 4.3792517347f,4.3797104713f,4.3801692068f,4.3806279412f,4.3810866744f,4.3815454065f, + 4.3820041374f,4.3824628672f,4.3829215958f,4.3833803233f,4.3838390496f,4.3842977748f, + 4.3847564989f,4.3852152218f,4.3856739436f,4.3861326643f,4.3865913838f,4.3870501021f, + 4.3875088194f,4.3879675355f,4.3884262505f,4.3888849643f,4.3893436770f,4.3898023886f, + 4.3902610991f,4.3907198084f,4.3911785166f,4.3916372237f,4.3920959296f,4.3925546345f, + 4.3930133382f,4.3934720407f,4.3939307422f,4.3943894426f,4.3948481418f,4.3953068399f, + 4.3957655369f,4.3962242328f,4.3966829276f,4.3971416212f,4.3976003138f,4.3980590052f, + 4.3985176955f,4.3989763847f,4.3994350728f,4.3998937599f,4.4003524457f,4.4008111305f, + 4.4012698142f,4.4017284968f,4.4021871783f,4.4026458587f,4.4031045380f,4.4035632162f, + 4.4040218932f,4.4044805692f,4.4049392441f,4.4053979179f,4.4058565907f,4.4063152623f, + 4.4067739328f,4.4072326022f,4.4076912706f,4.4081499378f,4.4086086040f,4.4090672691f, + 4.4095259331f,4.4099845960f,4.4104432579f,4.4109019186f,4.4113605783f,4.4118192369f, + 4.4122778944f,4.4127365508f,4.4131952062f,4.4136538605f,4.4141125137f,4.4145711658f, + 4.4150298169f,4.4154884669f,4.4159471158f,4.4164057636f,4.4168644104f,4.4173230561f, + 4.4177817008f,4.4182403443f,4.4186989868f,4.4191576283f,4.4196162687f,4.4200749080f, + 4.4205335463f,4.4209921835f,4.4214508196f,4.4219094547f,4.4223680887f,4.4228267217f, + 4.4232853536f,4.4237439845f,4.4242026143f,4.4246612431f,4.4251198708f,4.4255784975f, + 4.4260371231f,4.4264957476f,4.4269543712f,4.4274129936f,4.4278716151f,4.4283302355f, + 4.4287888548f,4.4292474731f,4.4297060904f,4.4301647066f,4.4306233218f,4.4310819359f, + 4.4315405490f,4.4319991611f,4.4324577721f,4.4329163821f,4.4333749911f,4.4338335991f, + 4.4342922060f,4.4347508119f,4.4352094167f,4.4356680205f,4.4361266233f,4.4365852251f, + 4.4370438258f,4.4375024256f,4.4379610243f,4.4384196220f,4.4388782186f,4.4393368143f, + 4.4397954089f,4.4402540025f,4.4407125951f,4.4411711866f,4.4416297772f,4.4420883667f, + 4.4425469553f,4.4430055428f,4.4434641293f,4.4439227148f,4.4443812993f,4.4448398828f, + 4.4452984652f,4.4457570467f,4.4462156272f,4.4466742066f,4.4471327851f,4.4475913625f, + 4.4161034198f,4.4166593240f,4.4172152283f,4.4177711325f,4.4183270368f,4.4188829410f, + 4.4194388453f,4.4199947496f,4.4205506538f,4.4211065581f,4.4216624623f,4.4222183666f, + 4.4227742708f,4.4233301751f,4.4238860794f,4.4244419836f,4.4249978879f,4.4255537921f, + 4.4261096964f,4.4266656006f,4.4272215049f,4.4277774092f,4.4283333134f,4.4288892177f, + 4.4294451219f,4.4300010262f,4.4305569304f,4.4311128347f,4.4316687390f,4.4322246432f, + 4.4327805475f,4.4333364517f,4.4338923560f,4.4344482602f,4.4350041645f,4.4355600688f, + 4.4361159730f,4.4366718773f,4.4372277815f,4.4377836858f,4.4383395900f,4.4388954943f, + 4.4394513986f,4.4400073028f,4.4405632071f,4.4411191113f,4.4416750156f,4.4422309198f, + 4.4427868241f,4.4433427284f,4.4438986326f,4.4444545369f,4.4450104411f,4.4455663454f, + 4.4461222496f,4.4466781539f,4.4472340582f,4.4477899624f,4.4483458667f,4.4489017709f, + 4.4494576752f,4.4500135794f,4.4505694837f,4.4511253880f,4.4516812922f,4.4522371965f, + 4.4527931007f,4.4533490050f,4.4539049093f,4.4544608135f,4.4550167178f,4.4555726220f, + 4.4561285263f,4.4566844305f,4.4572403348f,4.4577962391f,4.4583521433f,4.4589080476f, + 4.4594639518f,4.4600198561f,4.4605757603f,4.4611316646f,4.4616875689f,4.4622434731f, + 4.4627993774f,4.4633552816f,4.4639111859f,4.4644670901f,4.4650229944f,4.4655788987f, + 4.4661348029f,4.4666907072f,4.4672466114f,4.4678025157f,4.4683584199f,4.4689143242f, + 4.4694702285f,4.4700261327f,4.4705820370f,4.4711379412f,4.4716938455f,4.4722497497f, + 4.4728056540f,4.4733615583f,4.4739174625f,4.4744733668f,4.4750292710f,4.4755851753f, + 4.4761410795f,4.4766969838f,4.4772528881f,4.4778087923f,4.4783646966f,4.4789206008f, + 4.4794765051f,4.4800324093f,4.4805883136f,4.4811442179f,4.4817001221f,4.4822560264f, + 4.4828119306f,4.4833678349f,4.4839237391f,4.4844796434f,4.4850355477f,4.4855914519f, + 4.4861473562f,4.4867032604f,4.4872591647f,4.4878150689f,4.4883709732f,4.4889268775f, + 4.4894827817f,4.4900386860f,4.4905945902f,4.4911504945f,4.4917063987f,4.4922623030f, + 4.4928182073f,4.4933741115f,4.4939300158f,4.4944859200f,4.4950418243f,4.4955977285f, + 4.4961536328f,4.4967095371f,4.4972654413f,4.4978213456f,4.4983772498f,4.4989331541f, + 4.4994890583f,4.5000449626f,4.5006008669f,4.5011567711f,4.5017126754f,4.5022685796f, + 4.5028244839f,4.5033803881f,4.5039362924f,4.5044921967f,4.5050481009f,4.5056040052f, + 4.5061599094f,4.5067158137f,4.5072717179f,4.5078276222f,4.5083835265f,4.5089394307f, + 4.5094953350f,4.5100512392f,4.5106071435f,4.5111630477f,4.5117189520f,4.5122748563f, + 4.5128307605f,4.5133866648f,4.5139425690f,4.5144984733f,4.5150543776f,4.5156102818f, + 4.5161661861f,4.5167220903f,4.5172779946f,4.5178338988f,4.5183898031f,4.5189457074f, + 4.5195016116f,4.5200575159f,4.5206134201f,4.5211693244f,4.5217252286f,4.5222811329f, + 4.5228370372f,4.5233929414f,4.5239488457f,4.5245047499f,4.5250606542f,4.5256165584f, + 4.5261724627f,4.5267283670f,4.5272842712f,4.5278401755f,4.5283960797f,4.5289519840f, + 4.5295078882f,4.5300637925f,4.5306196968f,4.5311756010f,4.5317315053f,4.5322874095f, + 4.5328433138f,4.5333992180f,4.5339551223f,4.5345110266f,4.5350669308f,4.5356228351f, + 4.5361787393f,4.5367346436f,4.5372905478f,4.5378464521f,4.5384023564f,4.5389582606f, + 4.5395141649f,4.5400700691f,4.5406259734f,4.5411818776f,4.5417377819f,4.5422936862f, + 4.5428495904f,4.5434054947f,4.5439613989f,4.5445173032f,4.5450732074f,4.5456291117f, + 4.5461850160f,4.5467409202f,4.5472968245f,4.5478527287f,4.5484086330f,4.5489645372f, + 4.5495204415f,4.5500763458f,4.5506322500f,4.5511881543f,4.5517440585f,4.5522999628f, + 4.5528558670f,4.5534117713f,4.5539676756f,4.5545235798f,4.5550794841f,4.5556353883f, + 4.5561912926f,4.5567471968f,4.5573031011f,4.5578590054f,4.5584149096f,4.5589708139f, + 4.5595267181f,4.5600826224f,4.5606385266f,4.5611944309f,4.5617503352f,4.5623062394f, + 4.5628621437f,4.5634180479f,4.5639739522f,4.5645298564f,4.5650857607f,4.5656416650f, + 4.5661975692f,4.5667534735f,4.5673093777f,4.5678652820f,4.5684211862f,4.5689770905f, + 4.5695329948f,4.5700888990f,4.5706448033f,4.5712007075f,4.5717566118f,4.5723125160f, + 4.5728684203f,4.5734243246f,4.5739802288f,4.5745361331f,4.5750920373f,4.5756479416f, + 4.5762038458f,4.5767597501f,4.5773156544f,4.5778715586f,4.5784274629f,4.5789833671f, + 4.5795392714f,4.5800951757f,4.5806510799f,4.5812069842f,4.5817628884f,4.5823187927f, + 4.5828746969f,4.5834306012f,4.5839865055f,4.5845424097f,4.5850983140f,4.5856542182f, + 4.5862101225f,4.5867660267f,4.5873219310f,4.5878778353f,4.5884337395f,4.5889896438f, + 4.5895455480f,4.5901014523f,4.5906573565f,4.5912132608f,4.5917691651f,4.5923250693f, + 4.5928809736f,4.5934368778f,4.5939927821f,4.5945486863f,4.5951045906f,4.5956604949f, + 4.5962163991f,4.5967723034f,4.5973282076f,4.5978841119f,4.5984400161f,4.5989959204f, + 4.5995518247f,4.6001077289f,4.6006636332f,4.6012195374f,4.6017754417f,4.6023313459f, + 4.6028872502f,4.6034431545f,4.6039990587f,4.6045549630f,4.6051108672f,4.6056667715f, + 4.6062226757f,4.6067785800f,4.6073344843f,4.6078903885f,4.6084462928f,4.6090021970f, + 4.6095581013f,4.6101140055f,4.6106699098f,4.6112258141f,4.6117817183f,4.6123376226f, + 4.6128935268f,4.6134494311f,4.6140053353f,4.6145612396f,4.6151171439f,4.6156730481f, + 4.6162289524f,4.6167848566f,4.6173407609f,4.6178966651f,4.6184525694f,4.6190084737f, + 4.6195643779f,4.6201202822f,4.6206761864f,4.6212320907f,4.6217879949f,4.6223438992f, + 4.6228998035f,4.6234557077f,4.6240116120f,4.6245675162f,4.6251234205f,4.6256793247f, + 4.6262352290f,4.6267911333f,4.6273470375f,4.6279029418f,4.6284588460f,4.6290147503f, + 4.6295706545f,4.6301265588f,4.6306824631f,4.6312383673f,4.6317942716f,4.6323501758f, + 4.6329060801f,4.6334619843f,4.6340178886f,4.6345737929f,4.6351296971f,4.6356856014f, + 4.6362415056f,4.6367974099f,4.6373533141f,4.6379092184f,4.6384651227f,4.6390210269f, + 4.6395769312f,4.6401328354f,4.6406887397f,4.6412446440f,4.6418005482f,4.6423564525f, + 4.6429123567f,4.6434682610f,4.6440241652f,4.6445800695f,4.6451359738f,4.6456918780f, + 4.6462477823f,4.6468036865f,4.6473595908f,4.6479154950f,4.6484713993f,4.6490273036f, + 4.6495832078f,4.6501391121f,4.6506950163f,4.6512509206f,4.6518068248f,4.6523627291f, + 4.6529186334f,4.6534745376f,4.6540304419f,4.6545863461f,4.6551422504f,4.6556981546f, + 4.6562540589f,4.6568099632f,4.6573658674f,4.6579217717f,4.6584776759f,4.6590335802f, + 4.6595894844f,4.6601453887f,4.6607012930f,4.6612571972f,4.6618131015f,4.6623690057f, + 4.6629249100f,4.6634808142f,4.6640367185f,4.6645926228f,4.6651485270f,4.6657044313f, + 4.6662603355f,4.6668162398f,4.6673721440f,4.6679280483f,4.6684839526f,4.6690398568f, + 4.6695957611f,4.6701516653f,4.6707075696f,4.6712634738f,4.6718193781f,4.6723752824f, + 4.6729311866f,4.6734870909f,4.6740429951f,4.6745988994f,4.6751548036f,4.6757107079f, + 4.6762666122f,4.6768225164f,4.6773784207f,4.6779343249f,4.6784902292f,4.6790461334f, + 4.6796020377f,4.6801579420f,4.6807138462f,4.6812697505f,4.6818256547f,4.6823815590f, + 4.6829374632f,4.6834933675f,4.6840492718f,4.6846051760f,4.6851610803f,4.6857169845f, + 4.6862728888f,4.6868287930f,4.6873846973f,4.6879406016f,4.6884965058f,4.6890524101f, + 4.6896083143f,4.6901642186f,4.6907201228f,4.6912760271f,4.6918319314f,4.6923878356f, + 4.6929437399f,4.6934996441f,4.6940555484f,4.6946114526f,4.6951673569f,4.6957232612f, + 4.6962791654f,4.6968350697f,4.6973909739f,4.6979468782f,4.6985027824f,4.6990586867f, + 4.6996145910f,4.7001704952f,4.7007263995f,4.7012823037f,4.7018382080f,4.7023941123f, + 4.7029500165f,4.7035059208f,4.7040618250f,4.7046177293f,4.7051736335f,4.7057295378f, + 4.7062854421f,4.7068413463f,4.7073972506f,4.7079531548f,4.7085090591f,4.7090649633f, + 4.7096208676f,4.7101767719f,4.7107326761f,4.7112885804f,4.7118444846f,4.7124003889f, + 4.7129562931f,4.7135121974f,4.7140681017f,4.7146240059f,4.7151799102f,4.7157358144f, + 4.7162917187f,4.7168476229f,4.7174035272f,4.7179594315f,4.7185153357f,4.7190712400f, + 4.7196271442f,4.7201830485f,4.7207389527f,4.7212948570f,4.7218507613f,4.7224066655f, + 4.7229625698f,4.7235184740f,4.7240743783f,4.7246302825f,4.7251861868f,4.7257420911f, + 4.7262979953f,4.7268538996f,4.7274098038f,4.7279657081f,4.7285216123f,4.7290775166f, + 4.7296334209f,4.7301893251f,4.7307452294f,4.7313011336f,4.7318570379f,4.7324129421f, + 4.7329688464f,4.7335247507f,4.7340806549f,4.7346365592f,4.7351924634f,4.7357483677f, + 4.7363042719f,4.7368601762f,4.7374160805f,4.7379719847f,4.7385278890f,4.7390837932f, + 4.7396396975f,4.7401956017f,4.7407515060f,4.7413074103f,4.7418633145f,4.7424192188f, + 4.7429751230f,4.7435310273f,4.7440869315f,4.7446428358f,4.7451987401f,4.7457546443f, + 4.7463105486f,4.7468664528f,4.7474223571f,4.7479782613f,4.7485341656f,4.7490900699f, + 4.7496459741f,4.7502018784f,4.7507577826f,4.7513136869f,4.7518695911f,4.7524254954f, + 4.7529813997f,4.7535373039f,4.7540932082f,4.7546491124f,4.7552050167f,4.7557609209f, + 4.7563168252f,4.7568727295f,4.7574286337f,4.7579845380f,4.7585404422f,4.7590963465f, + 4.7596522507f,4.7602081550f,4.7607640593f,4.7613199635f,4.7618758678f,4.7624317720f, + 4.7629876763f,4.7635435806f,4.7640994848f,4.7646553891f,4.7652112933f,4.7657671976f, + 4.7663231018f,4.7668790061f,4.7674349104f,4.7679908146f,4.7685467189f,4.7691026231f, + 4.7696585274f,4.7702144316f,4.7707703359f,4.7713262402f,4.7718821444f,4.7724380487f, + 4.7729939529f,4.7735498572f,4.7741057614f,4.7746616657f,4.7752175700f,4.7757734742f, + 4.7763293785f,4.7768852827f,4.7774411870f,4.7779970912f,4.7785529955f,4.7791088998f, + 4.7796648040f,4.7802207083f,4.7807766125f,4.7813325168f,4.7818884210f,4.7824443253f, + 4.7830002296f,4.7835561338f,4.7841120381f,4.7846679423f,4.7852238466f,4.7857797508f, + 4.7863356551f,4.7868915594f,4.7874474636f,4.7880033679f,4.7885592721f,4.7891151764f, + 4.7896710806f,4.7902269849f,4.7907828892f,4.7913387934f,4.7918946977f,4.7924506019f, + 4.7930065062f,4.7935624104f,4.7941183147f,4.7946742190f,4.7952301232f,4.7957860275f, + 4.7963419317f,4.7968978360f,4.7974537402f,4.7980096445f,4.7985655488f,4.7991214530f, + 4.7996773573f,4.8002332615f,4.8007891658f,4.8013450700f,4.8019009743f,4.8024568786f, + 4.8030127828f,4.8035686871f,4.8041245913f,4.8046804956f,4.8052363998f,4.8057923041f, + 4.8063482084f,4.8069041126f,4.8074600169f,4.8080159211f,4.8085718254f,4.8091277296f, + 4.8096836339f,4.8102395382f,4.8107954424f,4.8113513467f,4.8119072509f,4.8124631552f, + 4.8130190594f,4.8135749637f,4.8141308680f,4.8146867722f,4.8152426765f,4.8157985807f, + 4.8163544850f,4.8169103892f,4.8174662935f,4.8180221978f,4.8185781020f,4.8191340063f, + 4.8196899105f,4.8202458148f,4.8208017190f,4.8213576233f,4.8219135276f,4.8224694318f, + 4.8230253361f,4.8235812403f,4.8241371446f,4.8246930488f,4.8252489531f,4.8258048574f, + 4.8263607616f,4.8269166659f,4.8274725701f,4.8280284744f,4.8285843787f,4.8291402829f, + 4.8296961872f,4.8302520914f,4.8308079957f,4.8313638999f,4.8319198042f,4.8324757085f, + 4.8330316127f,4.8335875170f,4.8341434212f,4.8346993255f,4.8352552297f,4.8358111340f, + 4.8363670383f,4.8369229425f,4.8374788468f,4.8380347510f,4.8385906553f,4.8391465595f, + 4.8397024638f,4.8402583681f,4.8408142723f,4.8413701766f,4.8419260808f,4.8424819851f, + 4.8430378893f,4.8435937936f,4.8441496979f,4.8447056021f,4.8452615064f,4.8458174106f, + 4.8463733149f,4.8469292191f,4.8474851234f,4.8480410277f,4.8485969319f,4.8491528362f, + 4.8497087404f,4.8502646447f,4.8508205489f,4.8513764532f,4.8519323575f,4.8524882617f, + 4.8530441660f,4.8536000702f,4.8541559745f,4.8547118787f,4.8552677830f,4.8558236873f, + 4.8563795915f,4.8569354958f,4.8574914000f,4.8580473043f,4.8586032085f,4.8591591128f, + 4.8597150171f,4.8602709213f,4.8608268256f,4.8613827298f,4.8619386341f,4.8624945383f, + 4.8630504426f,4.8636063469f,4.8641622511f,4.8647181554f,4.8652740596f,4.8658299639f, + 4.8663858681f,4.8669417724f,4.8674976767f,4.8680535809f,4.8686094852f,4.8691653894f, + 4.8697212937f,4.8702771979f,4.8708331022f,4.8713890065f,4.8719449107f,4.8725008150f, + 4.8730567192f,4.8736126235f,4.8741685277f,4.8747244320f,4.8752803363f,4.8758362405f, + 4.8763921448f,4.8769480490f,4.8775039533f,4.8780598575f,4.8786157618f,4.8791716661f, + 4.8797275703f,4.8802834746f,4.8808393788f,4.8813952831f,4.8819511873f,4.8825070916f, + 4.8830629959f,4.8836189001f,4.8841748044f,4.8847307086f,4.8852866129f,4.8858425171f, + 4.8863984214f,4.8869543257f,4.8875102299f,4.8880661342f,4.8886220384f,4.8891779427f, + 4.8897338470f,4.8902897512f,4.8908456555f,4.8914015597f,4.8919574640f,4.8925133682f, + 4.8930692725f,4.8936251768f,4.8941810810f,4.8947369853f,4.8952928895f,4.8958487938f, + 4.8964046980f,4.8969606023f,4.8975165066f,4.8980724108f,4.8986283151f,4.8991842193f, + 4.8997401236f,4.9002960278f,4.9008519321f,4.9014078364f,4.9019637406f,4.9025196449f, + 4.9030755491f,4.9036314534f,4.9041873576f,4.9047432619f,4.9052991662f,4.9058550704f, + 4.9064109747f,4.9069668789f,4.9075227832f,4.9080786874f,4.9086345917f,4.9091904960f, + 4.9097464002f,4.9103023045f,4.9108582087f,4.9114141130f,4.9119700172f,4.9125259215f, + 4.9130818258f,4.9136377300f,4.9141936343f,4.9147495385f,4.9153054428f,4.9158613470f, + 4.9164172513f,4.9169731556f,4.9175290598f,4.9180849641f,4.9186408683f,4.9191967726f, + 4.9197526768f,4.9203085811f,4.9208644854f,4.9214203896f,4.9219762939f,4.9225321981f, + 4.9230881024f,4.9236440066f,4.9241999109f,4.9247558152f,4.9253117194f,4.9258676237f, + 4.9264235279f,4.9269794322f,4.9275353364f,4.9280912407f,4.9286471450f,4.9292030492f, + 4.9297589535f,4.9303148577f,4.9308707620f,4.9314266662f,4.9319825705f,4.9325384748f, + 4.9330943790f,4.9336502833f,4.9342061875f,4.9347620918f,4.9353179960f,4.9358739003f, + 4.9364298046f,4.9369857088f,4.9375416131f,4.9380975173f,4.9386534216f,4.9392093258f, + 4.9397652301f,4.9403211344f,4.9408770386f,4.9414329429f,4.9419888471f,4.9425447514f, + 4.9431006556f,4.9436565599f,4.9442124642f,4.9447683684f,4.9453242727f,4.9458801769f, + 4.9464360812f,4.9469919854f,4.9475478897f,4.9481037940f,4.9486596982f,4.9492156025f, + 4.9497715067f,4.9503274110f,4.9508833153f,4.9514392195f,4.9519951238f,4.9525510280f, + 4.9531069323f,4.9536628365f,4.9542187408f,4.9547746451f,4.9553305493f,4.9558864536f, + 4.9564423578f,4.9569982621f,4.9575541663f,4.9581100706f,4.9586659749f,4.9592218791f, + 4.9597777834f,4.9603336876f,4.9608895919f,4.9614454961f,4.9620014004f,4.9625573047f, + 4.9631132089f,4.9636691132f,4.9642250174f,4.9647809217f,4.9653368259f,4.9658927302f, + 4.9664486345f,4.9670045387f,4.9675604430f,4.9681163472f,4.9686722515f,4.9692281557f, + 4.9697840600f,4.9703399643f,4.9708958685f,4.9714517728f,4.9720076770f,4.9725635813f, + 4.9731194855f,4.9736753898f,4.9742312941f,4.9747871983f,4.9753431026f,4.9758990068f, + 4.9764549111f,4.9770108153f,4.9775667196f,4.9781226239f,4.9786785281f,4.9792344324f, + 4.9797903366f,4.9803462409f,4.9809021451f,4.9814580494f,4.9820139537f,4.9825698579f, + 4.9831257622f,4.9836816664f,4.9842375707f,4.9847934749f,4.9853493792f,4.9859052835f, + 4.9864611877f,4.9870170920f,4.9875729962f,4.9881289005f,4.9886848047f,4.9892407090f, + 4.9897966133f,4.9903525175f,4.9909084218f,4.9914643260f,4.9920202303f,4.9925761345f, + 4.9931320388f,4.9936879431f,4.9942438473f,4.9947997516f,4.9953556558f,4.9959115601f, + 4.9964674643f,4.9970233686f,4.9975792729f,4.9981351771f,4.9986910814f,4.9992469856f, + 4.9998028899f,5.0003587941f,5.0009146984f,5.0014706027f,5.0020265069f,5.0025824112f, + 5.0031383154f,5.0036942197f,5.0042501239f,5.0048060282f,5.0053619325f,5.0059178367f, + 5.0064737410f,5.0070296452f,5.0075855495f,5.0081414537f,5.0086973580f,5.0092532623f, + 5.0098091665f,5.0103650708f,5.0109209750f,5.0114768793f,5.0120327835f,5.0125886878f, + 5.0131445921f,5.0137004963f,5.0142564006f,5.0148123048f,5.0153682091f,5.0159241134f, + 5.0164800176f,5.0170359219f,5.0175918261f,5.0181477304f,5.0187036346f,5.0192595389f, + 5.0198154432f,5.0203713474f,5.0209272517f,5.0214831559f,5.0220390602f,5.0225949644f, + 5.0231508687f,5.0237067730f,5.0242626772f,5.0248185815f,5.0253744857f,5.0259303900f, + 5.0264862942f,5.0270421985f,5.0275981028f,5.0281540070f,5.0287099113f,5.0292658155f, + 5.0298217198f,5.0303776240f,5.0309335283f,5.0314894326f,5.0320453368f,5.0326012411f, + 5.0331571453f,5.0337130496f,5.0342689538f,5.0348248581f,5.0353807624f,5.0359366666f, + 5.0364925709f,5.0370484751f,5.0376043794f,5.0381602836f,5.0387161879f,5.0392720922f, + 5.0398279964f,5.0403839007f,5.0409398049f,5.0414957092f,5.0420516134f,5.0426075177f, + 5.0431634220f,5.0437193262f,5.0442752305f,5.0448311347f,5.0453870390f,5.0459429432f, + 5.0464988475f,5.0470547518f,5.0476106560f,5.0481665603f,5.0487224645f,5.0492783688f, + 5.0498342730f,5.0503901773f,5.0509460816f,5.0515019858f,5.0520578901f,5.0526137943f, + 5.0531696986f,5.0537256028f,5.0542815071f,5.0548374114f,5.0553933156f,5.0559492199f, + 5.0565051241f,5.0570610284f,5.0576169326f,5.0581728369f,5.0587287412f,5.0592846454f, + 5.0598405497f,5.0603964539f,5.0609523582f,5.0615082624f,5.0620641667f,5.0626200710f, + 5.0631759752f,5.0637318795f,5.0642877837f,5.0648436880f,5.0653995922f,5.0659554965f, + 5.0665114008f,5.0670673050f,5.0676232093f,5.0681791135f,5.0687350178f,5.0692909220f, + 5.0698468263f,5.0704027306f,5.0709586348f,5.0715145391f,5.0720704433f,5.0726263476f, + 5.0731822518f,5.0737381561f,5.0742940604f,5.0748499646f,5.0754058689f,5.0759617731f, + 5.0765176774f,5.0770735817f,5.0776294859f,5.0781853902f,5.0787412944f,5.0792971987f, + 5.0798531029f,5.0804090072f,5.0809649115f,5.0815208157f,5.0820767200f,5.0826326242f, + 5.0831885285f,5.0837444327f,5.0843003370f,5.0848562413f,5.0854121455f,5.0859680498f, + 5.0865239540f,5.0870798583f,5.0876357625f,5.0881916668f,5.0887475711f,5.0893034753f, + 5.0898593796f,5.0904152838f,5.0909711881f,5.0915270923f,5.0920829966f,5.0926389009f, + 5.0931948051f,5.0937507094f,5.0943066136f,5.0948625179f,5.0954184221f,5.0959743264f, + 5.0965302307f,5.0970861349f,5.0976420392f,5.0981979434f,5.0987538477f,5.0993097519f, + 5.0998656562f,5.1004215605f,5.1009774647f,5.1015333690f,5.1020892732f,5.1026451775f, + 5.1032010817f,5.1037569860f,5.1043128903f,5.1048687945f,5.1054246988f,5.1059806030f, + 5.1065365073f,5.1070924115f,5.1076483158f,5.1082042201f,5.1087601243f,5.1093160286f, + 5.1098719328f,5.1104278371f,5.1109837413f,5.1115396456f,5.1120955499f,5.1126514541f, + 5.1132073584f,5.1137632626f,5.1143191669f,5.1148750711f,5.1154309754f,5.1159868797f, + 5.1165427839f,5.1170986882f,5.1176545924f,5.1182104967f,5.1187664009f,5.1193223052f, + 5.1198782095f,5.1204341137f,5.1209900180f,5.1215459222f,5.1221018265f,5.1226577307f, + 5.1232136350f,5.1237695393f,5.1243254435f,5.1248813478f,5.1254372520f,5.1259931563f, + 5.1265490605f,5.1271049648f,5.1276608691f,5.1282167733f,5.1287726776f,5.1293285818f, + 5.1298844861f,5.1304403903f,5.1309962946f,5.1315521989f,5.1321081031f,5.1326640074f, + 5.1332199116f,5.1337758159f,5.1343317201f,5.1348876244f,5.1354435287f,5.1359994329f, + 5.1365553372f,5.1371112414f,5.1376671457f,5.1382230500f,5.1387789542f,5.1393348585f, + 5.1398907627f,5.1404466670f,5.1410025712f,5.1415584755f,5.1421143798f,5.1426702840f, + 5.1432261883f,5.1437820925f,5.1443379968f,5.1448939010f,5.1454498053f,5.1460057096f, + 5.1465616138f,5.1471175181f,5.1476734223f,5.1482293266f,5.1487852308f,5.1493411351f, + 5.1498970394f,5.1504529436f,5.1510088479f,5.1515647521f,5.1521206564f,5.1526765606f, + 5.1532324649f,5.1537883692f,5.1543442734f,5.1549001777f,5.1554560819f,5.1560119862f, + 5.1565678904f,5.1571237947f,5.1576796990f,5.1582356032f,5.1587915075f,5.1593474117f, + 5.1599033160f,5.1604592202f,5.1610151245f,5.1615710288f,5.1621269330f,5.1626828373f, + 5.1632387415f,5.1637946458f,5.1643505500f,5.1649064543f,5.1654623586f,5.1660182628f, + 5.1665741671f,5.1671300713f,5.1676859756f,5.1682418798f,5.1687977841f,5.1693536884f, + 5.1699095926f,5.1704654969f,5.1710214011f,5.1715773054f,5.1721332096f,5.1726891139f, + 5.1732450182f,5.1738009224f,5.1743568267f,5.1749127309f,5.1754686352f,5.1760245394f, + 5.1765804437f,5.1771363480f,5.1776922522f,5.1782481565f,5.1788040607f,5.1793599650f, + 5.1799158692f,5.1804717735f,5.1810276778f,5.1815835820f,5.1821394863f,5.1826953905f, + 5.1832512948f,5.1838071990f,5.1843631033f,5.1849190076f,5.1854749118f,5.1860308161f, + 5.1865867203f,5.1871426246f,5.1876985288f,5.1882544331f,5.1888103374f,5.1893662416f, + 5.1899221459f,5.1904780501f,5.1910339544f,5.1915898586f,5.1921457629f,5.1927016672f, + 5.1932575714f,5.1938134757f,5.1943693799f,5.1949252842f,5.1954811884f,5.1960370927f, + 5.1965929970f,5.1971489012f,5.1977048055f,5.1982607097f,5.1988166140f,5.1993725183f, + 5.1999284225f,5.2004843268f,5.2010402310f,5.2015961353f,5.2021520395f,5.2027079438f, + 5.2032638481f,5.2038197523f,5.2043756566f,5.2049315608f,5.2054874651f,5.2060433693f, + 5.2065992736f,5.2071551779f,5.2077110821f,5.2082669864f,5.2088228906f,5.2093787949f, + 5.2099346991f,5.2104906034f,5.2110465077f,5.2116024119f,5.2121583162f,5.2127142204f, + 5.2132701247f,5.2138260289f,5.2143819332f,5.2149378375f,5.2154937417f,5.2160496460f, + 5.2166055502f,5.2171614545f,5.2177173587f,5.2182732630f,5.2188291673f,5.2193850715f, + 5.2199409758f,5.2204968800f,5.2210527843f,5.2216086885f,5.2221645928f,5.2227204971f, + 5.2232764013f,5.2238323056f,5.2243882098f,5.2249441141f,5.2255000183f,5.2260559226f, + 5.2266118269f,5.2271677311f,5.2277236354f,5.2282795396f,5.2288354439f,5.2293913481f, + 5.2299472524f,5.2305031567f,5.2310590609f,5.2316149652f,5.2321708694f,5.2327267737f, + 5.2332826779f,5.2338385822f,5.2343944865f,5.2349503907f,5.2355062950f,5.2360621992f, + 5.2366181035f,5.2371740077f,5.2377299120f,5.2382858163f,5.2388417205f,5.2393976248f, + 5.2399535290f,5.2405094333f,5.2410653375f,5.2416212418f,5.2421771461f,5.2427330503f, + 5.2432889546f,5.2438448588f,5.2444007631f,5.2449566673f,5.2455125716f,5.2460684759f, + 5.2466243801f,5.2471802844f,5.2477361886f,5.2482920929f,5.2488479971f,5.2494039014f, + 5.2499598057f,5.2505157099f,5.2510716142f,5.2516275184f,5.2521834227f,5.2527393269f, + 5.2532952312f,5.2538511355f,5.2544070397f,5.2549629440f,5.2555188482f,5.2560747525f, + 5.2566306567f,5.2571865610f,5.2577424653f,5.2582983695f,5.2588542738f,5.2594101780f, + 5.2599660823f,5.2605219865f,5.2610778908f,5.2616337951f,5.2621896993f,5.2627456036f, + 5.2633015078f,5.2638574121f,5.2644133164f,5.2649692206f,5.2655251249f,5.2660810291f, + 5.2666369334f,5.2671928376f,5.2677487419f,5.2683046462f,5.2688605504f,5.2694164547f, + 5.2699723589f,5.2705282632f,5.2710841674f,5.2716400717f,5.2721959760f,5.2727518802f, + 5.2733077845f,5.2738636887f,5.2744195930f,5.2749754972f,5.2755314015f,5.2760873058f, + 5.2766432100f,5.2771991143f,5.2777550185f,5.2783109228f,5.2788668270f,5.2794227313f, + 5.2799786356f,5.2805345398f,5.2810904441f,5.2816463483f,5.2822022526f,5.2827581568f, + 5.2833140611f,5.2838699654f,5.2844258696f,5.2849817739f,5.2855376781f,5.2860935824f, + 5.2866494866f,5.2872053909f,5.2877612952f,5.2883171994f,5.2888731037f,5.2894290079f, + 5.2899849122f,5.2905408164f,5.2910967207f,5.2916526250f,5.2922085292f,5.2927644335f, + 5.2933203377f,5.2938762420f,5.2944321462f,5.2949880505f,5.2955439548f,5.2960998590f, + 5.2966557633f,5.2972116675f,5.2977675718f,5.2983234760f,5.2988793803f,5.2994352846f, + 5.2999911888f,5.3005470931f,5.3011029973f,5.3016589016f,5.3022148058f,5.3027707101f, + 5.3033266144f,5.3038825186f,5.3044384229f,5.3049943271f,5.3055502314f,5.3061061356f, + 5.3066620399f,5.3072179442f,5.3077738484f,5.3083297527f,5.3088856569f,5.3094415612f, + 5.3099974654f,5.3105533697f,5.3111092740f,5.3116651782f,5.3122210825f,5.3127769867f, + 5.3133328910f,5.3138887952f,5.3144446995f,5.3150006038f,5.3155565080f,5.3161124123f, + 5.3166683165f,5.3172242208f,5.3177801250f,5.3183360293f,5.3188919336f,5.3194478378f, + 5.3200037421f,5.3205596463f,5.3211155506f,5.3216714548f,5.3222273591f,5.3227832634f, + 5.3233391676f,5.3238950719f,5.3244509761f,5.3250068804f,5.3255627847f,5.3261186889f, + 5.3266745932f,5.3272304974f,5.3277864017f,5.3283423059f,5.3288982102f,5.3294541145f, + 5.3300100187f,5.3305659230f,5.3311218272f,5.3316777315f,5.3322336357f,5.3327895400f, + 5.3333454443f,5.3339013485f,5.3344572528f,5.3350131570f,5.3355690613f,5.3361249655f, + 5.3366808698f,5.3372367741f,5.3377926783f,5.3383485826f,5.3389044868f,5.3394603911f, + 5.3400162953f,5.3405721996f,5.3411281039f,5.3416840081f,5.3422399124f,5.3427958166f, + 5.3433517209f,5.3439076251f,5.3444635294f,5.3450194337f,5.3455753379f,5.3461312422f, + 5.3466871464f,5.3472430507f,5.3477989549f,5.3483548592f,5.3489107635f,5.3494666677f, + 5.3500225720f,5.3505784762f,5.3511343805f,5.3516902847f,5.3522461890f,5.3528020933f, + 5.3533579975f,5.3539139018f,5.3544698060f,5.3550257103f,5.3555816145f,5.3561375188f, + 5.3566934231f,5.3572493273f,5.3578052316f,5.3583611358f,5.3589170401f,5.3594729443f, + 5.3600288486f,5.3605847529f,5.3611406571f,5.3616965614f,5.3622524656f,5.3628083699f, + 5.3633642741f,5.3639201784f,5.3644760827f,5.3650319869f,5.3655878912f,5.3661437954f, + 5.3666996997f,5.3672556039f,5.3678115082f,5.3683674125f,5.3689233167f,5.3694792210f, + 5.3700351252f,5.3705910295f,5.3711469337f,5.3717028380f,5.3722587423f,5.3728146465f, + 5.3733705508f,5.3739264550f,5.3744823593f,5.3750382635f,5.3755941678f,5.3761500721f, + 5.3767059763f,5.3772618806f,5.3778177848f,5.3783736891f,5.3789295933f,5.3794854976f, + 5.3800414019f,5.3805973061f,5.3811532104f,5.3817091146f,5.3822650189f,5.3828209231f, + 5.3833768274f,5.3839327317f,5.3844886359f,5.3850445402f,5.3856004444f,5.3861563487f, + 5.3867122530f,5.3872681572f,5.3878240615f,5.3883799657f,5.3889358700f,5.3894917742f, + 5.3900476785f,5.3906035828f,5.3911594870f,5.3917153913f,5.3922712955f,5.3928271998f, + 5.3933831040f,5.3939390083f,5.3944949126f,5.3950508168f,5.3956067211f,5.3961626253f, + 5.3967185296f,5.3972744338f,5.3978303381f,5.3983862424f,5.3989421466f,5.3994980509f, + 5.4000539551f,5.4006098594f,5.4011657636f,5.4017216679f,5.4022775722f,5.4028334764f, + 5.4033893807f,5.4039452849f,5.4045011892f,5.4050570934f,5.4056129977f,5.4061689020f, + 5.4067248062f,5.4072807105f,5.4078366147f,5.4083925190f,5.4089484232f,5.4095043275f, + 5.4100602318f,5.4106161360f,5.4111720403f,5.4117279445f,5.4122838488f,5.4128397530f, + 5.4133956573f,5.4139515616f,5.4145074658f,5.4150633701f,5.4156192743f,5.4161751786f, + 5.4167310828f,5.4172869871f,5.4178428914f,5.4183987956f,5.4189546999f,5.4195106041f, + 5.4200665084f,5.4206224126f,5.4211783169f,5.4217342212f,5.4222901254f,5.4228460297f, + 5.4234019339f,5.4239578382f,5.4245137424f,5.4250696467f,5.4256255510f,5.4261814552f, + 5.4267373595f,5.4272932637f,5.4278491680f,5.4284050722f,5.4289609765f,5.4295168808f, + 5.4300727850f,5.4306286893f,5.4311845935f,5.4317404978f,5.4322964020f,5.4328523063f, + 5.4334082106f,5.4339641148f,5.4345200191f,5.4350759233f,5.4356318276f,5.4361877318f, + 5.4367436361f,5.4372995404f,5.4378554446f,5.4384113489f,5.4389672531f,5.4395231574f, + 5.4400790616f,5.4406349659f,5.4411908702f,5.4417467744f,5.4423026787f,5.4428585829f, + 5.4434144872f,5.4439703914f,5.4445262957f,5.4450822000f,5.4456381042f,5.4461940085f, + 5.4467499127f,5.4473058170f,5.4478617212f,5.4484176255f,5.4489735298f,5.4495294340f, + 5.4500853383f,5.4506412425f,5.4511971468f,5.4517530511f,5.4523089553f,5.4528648596f, + 5.4534207638f,5.4539766681f,5.4545325723f,5.4550884766f,5.4556443809f,5.4562002851f, + 5.4567561894f,5.4573120936f,5.4578679979f,5.4584239021f,5.4589798064f,5.4595357107f, + 5.4600916149f,5.4606475192f,5.4612034234f,5.4617593277f,5.4623152319f,5.4628711362f, + 5.4634270405f,5.4639829447f,5.4645388490f,5.4650947532f,5.4656506575f,5.4662065617f, + 5.4667624660f,5.4673183703f,5.4678742745f,5.4684301788f,5.4689860830f,5.4695419873f, + 5.4700978915f,5.4706537958f,5.4712097001f,5.4717656043f,5.4723215086f,5.4728774128f, + 5.4734333171f,5.4739892213f,5.4745451256f,5.4751010299f,5.4756569341f,5.4762128384f, + 5.4767687426f,5.4773246469f,5.4778805511f,5.4784364554f,5.4789923597f,5.4795482639f, + 5.4801041682f,5.4806600724f,5.4812159767f,5.4817718809f,5.4823277852f,5.4828836895f, + 5.4834395937f,5.4839954980f,5.4845514022f,5.4851073065f,5.4856632107f,5.4862191150f, + 5.4867750193f,5.4873309235f,5.4878868278f,5.4884427320f,5.4889986363f,5.4895545405f, + 5.4901104448f,5.4906663491f,5.4912222533f,5.4917781576f,5.4923340618f,5.4928899661f, + 5.4934458703f,5.4940017746f,5.4945576789f,5.4951135831f,5.4956694874f,5.4962253916f, + 5.4967812959f,5.4973372001f,5.4978931044f,5.4984490087f,5.4990049129f,5.4995608172f, + 5.5001167214f,5.5006726257f,5.5012285299f,5.5017844342f,5.5023403385f,5.5028962427f, + 5.5034521470f,5.5040080512f,5.5045639555f,5.5051198597f,5.5056757640f,5.5062316683f, + 5.5067875725f,5.5073434768f,5.5078993810f,5.5084552853f,5.5090111895f,5.5095670938f, + 5.5101229981f,5.5106789023f,5.5112348066f,5.5117907108f,5.5123466151f,5.5129025194f, + 5.5134584236f,5.5140143279f,5.5145702321f,5.5151261364f,5.5156820406f,5.5162379449f, + 5.5167938492f,5.5173497534f,5.5179056577f,5.5184615619f,5.5190174662f,5.5195733704f, + 5.5201292747f,5.5206851790f,5.5212410832f,5.5217969875f,5.5223528917f,5.5229087960f, + 5.5234647002f,5.5240206045f,5.5245765088f,5.5251324130f,5.5256883173f,5.5262442215f, + 5.5268001258f,5.5273560300f,5.5279119343f,5.5284678386f,5.5290237428f,5.5295796471f, + 5.5301355513f,5.5306914556f,5.5312473598f,5.5318032641f,5.5323591684f,5.5329150726f, + 5.5334709769f,5.5340268811f,5.5345827854f,5.5351386896f,5.5356945939f,5.5362504982f, + 5.5368064024f,5.5373623067f,5.5379182109f,5.5384741152f,5.5390300194f,5.5395859237f, + 5.5401418280f,5.5406977322f,5.5412536365f,5.5418095407f,5.5423654450f,5.5429213492f, + 5.5434772535f,5.5440331578f,5.5445890620f,5.5451449663f,5.5457008705f,5.5462567748f, + 5.5468126790f,5.5473685833f,5.5479244876f,5.5484803918f,5.5490362961f,5.5495922003f, + 5.5501481046f,5.5507040088f,5.5512599131f,5.5518158174f,5.5523717216f,5.5529276259f, + 5.5534835301f,5.5540394344f,5.5545953386f,5.5551512429f,5.5557071472f,5.5562630514f, + 5.5568189557f,5.5573748599f,5.5579307642f,5.5584866684f, +}; +static float get_threshold_vs_intensity(float value) { + SkASSERT(value >= 0.0f); + SkASSERT(value < 100.0f); + return gTVITable[(int)(value * 100.0f)]; +} + +static float gVisualMaskTable[] = { + 1.0000000000f,1.1897247198f,1.6811970813f,2.1839523495f,2.6547752201f,3.0963096336f, + 3.5139692055f,3.9121076634f,4.2939844505f,4.6620559235f,5.0182118232f,5.3639381283f, + 5.7004270140f,6.0286520228f,6.3494205286f,6.6634111728f,6.9712011485f,7.2732864768f, + 7.5700973467f,7.8620099053f,8.1493554541f,8.4324277147f,8.7114886393f,8.9867731060f, + 9.2584927519f,9.5268391297f,9.7919863293f,10.0540931710f,10.3133050540f,10.5697555236f, + 10.8235676089f,11.0748549704f,11.3237228897f,11.5702691275f,11.8145846707f,12.0567543856f, + 12.2968575912f,12.5349685653f,12.7711569910f,13.0054883541f,13.2380242961f,13.4688229298f, + 13.6979391221f,13.9254247479f,14.1513289185f,14.3756981881f,14.5985767405f,14.8200065586f, + 15.0400275782f,15.2586778282f,15.4759935584f,15.6920093571f,15.9067582574f,16.1202718361f, + 16.3325803041f,16.5437125892f,16.7536964134f,16.9625583630f,17.1703239549f,17.3770176970f, + 17.5826631441f,17.7872829508f,17.9908989195f,18.1935320461f,18.3952025619f,18.5959299728f, + 18.7957330963f,18.9946300956f,19.1926385122f,19.3897752951f,19.5860568301f,19.7814989656f, + 19.9761170379f,20.1699258942f,20.3629399152f,20.5551730353f,20.7466387627f,20.9373501975f, + 21.1273200494f,21.3165606539f,21.5050839883f,21.6929016861f,21.8800250510f,22.0664650704f, + 22.2522324278f,22.4373375146f,22.6217904416f,22.8056010496f,22.9887789198f,23.1713333835f, + 23.3532735311f,23.5346082210f,23.7153460884f,23.8954955528f,24.0750648258f,24.2540619184f, + 24.4324946480f,24.6103706451f,24.7876973593f,24.9644820659f,25.1407318713f,25.3164537188f, + 25.4916543937f,25.6663405286f,25.8405186082f,26.0141949740f,26.1873758287f,26.3600672409f, + 26.5322751485f,26.7040053635f,26.8752635753f,27.0460553544f,27.2163861562f,27.3862613242f, + 27.5556860931f,27.7246655923f,27.8932048488f,28.0613087898f,28.2289822461f,28.3962299542f, + 28.5630565593f,28.7294666176f,28.8954645988f,29.0610548887f,29.2262417907f,29.3910295289f, + 29.5554222497f,29.7194240236f,29.8830388479f,30.0462706478f,30.2091232789f,30.3716005284f, + 30.5337061172f,30.6954437015f,30.8568168742f,31.0178291668f,31.1784840505f,31.3387849380f, + 31.4987351846f,31.6583380900f,31.8175968991f,31.9765148037f,32.1350949435f,32.2933404076f, + 32.4512542352f,32.6088394171f,32.7660988969f,32.9230355715f,33.0796522929f,33.2359518686f, + 33.3919370628f,33.5476105975f,33.7029751531f,33.8580333698f,34.0127878477f,34.1672411485f, + 34.3213957956f,34.4752542755f,34.6288190382f,34.7820924981f,34.9350770346f,35.0877749931f, + 35.2401886853f,35.3923203904f,35.5441723552f,35.6957467952f,35.8470458949f,35.9980718087f, + 36.1488266612f,36.2993125482f,36.4495315367f,36.5994856659f,36.7491769476f,36.8986073668f, + 37.0477788820f,37.1966934258f,37.3453529055f,37.4937592036f,37.6419141780f,37.7898196627f, + 37.9374774680f,38.0848893814f,38.2320571673f,38.3789825683f,38.5256673046f,38.6721130753f, + 38.8183215582f,38.9642944105f,39.1100332690f,39.2555397503f,39.4008154516f,39.5458619506f, + 39.6906808062f,39.8352735585f,39.9796417293f,40.1237868223f,40.2677103236f,40.4114137019f, + 40.5548984086f,40.6981658783f,40.8412175291f,40.9840547628f,41.1266789651f,41.2690915061f, + 41.4112937401f,41.5532870064f,41.6950726292f,41.8366519180f,41.9780261675f,42.1191966584f, + 42.2601646571f,42.4009314163f,42.5414981750f,42.6818661588f,42.8220365799f,42.9620106376f, + 43.1017895185f,43.2413743965f,43.3807664328f,43.5199667769f,43.6589765656f,43.7977969244f, + 43.9364289667f,44.0748737945f,44.2131324986f,44.3512061583f,44.4890958421f,44.6268026076f, + 44.7643275016f,44.9016715605f,45.0388358101f,45.1758212661f,45.3126289340f,45.4492598095f, + 45.5857148782f,45.7219951164f,45.8581014904f,45.9940349575f,46.1297964654f,46.2653869528f, + 46.4008073495f,46.5360585761f,46.6711415446f,46.8060571584f,46.9408063121f,47.0753898922f, + 47.2098087766f,47.3440638352f,47.4781559296f,47.6120859137f,47.7458546332f,47.8794629262f, + 48.0129116232f,48.1462015470f,48.2793335128f,48.4123083288f,48.5451267955f,48.6777897064f, + 48.8102978481f,48.9426519997f,49.0748529340f,49.2069014164f,49.3387982059f,49.4705440547f, + 49.6021397085f,49.7335859065f,49.8648833815f,49.9960328598f,50.1270350617f,50.2578907012f, + 50.3886004861f,50.5191651183f,50.6495852939f,50.7798617027f,50.9099950290f,51.0399859514f, + 51.1698351425f,51.2995432697f,51.4291109947f,51.5585389736f,51.6878278571f,51.8169782909f, + 51.9459909150f,52.0748663644f,52.2036052690f,52.3322082533f,52.4606759371f,52.5890089350f, + 52.7172078569f,52.8452733075f,52.9732058871f,53.1010061908f,53.2286748093f,53.3562123287f, + 53.4836193301f,53.6108963906f,53.7380440824f,53.8650629734f,53.9919536271f,54.1187166027f, + 54.2453524549f,54.3718617344f,54.4982449875f,54.6245027566f,54.7506355796f,54.8766439908f, + 55.0025285200f,55.1282896933f,55.2539280328f,55.3794440568f,55.5048382795f,55.6301112115f, + 55.7552633595f,55.8802952267f,56.0052073123f,56.1300001119f,56.2546741177f,56.3792298181f, + 56.5036676981f,56.6279882391f,56.7521919190f,56.8762792124f,57.0002505904f,57.1241065208f, + 57.2478474679f,57.3714738931f,57.4949862540f,57.6183850054f,57.7416705987f,57.8648434822f, + 57.9879041011f,58.1108528974f,58.2336903102f,58.3564167752f,58.4790327256f,58.6015385911f, + 58.7239347990f,58.8462217731f,58.9683999348f,59.0904697023f,59.2124314911f,59.3342857139f, + 59.4560327806f,59.5776730984f,59.6992070716f,59.8206351019f,59.9419575885f,60.0631749276f, + 60.1842875131f,60.3052957360f,60.4261999850f,60.5470006461f,60.6676981027f,60.7882927359f, + 60.9087849242f,61.0291750436f,61.1494634678f,61.2696505679f,61.3897367128f,61.5097222688f, + 61.6296076002f,61.7493930687f,61.8690790336f,61.9886658524f,62.1081538797f,62.2275434684f, + 62.3468349689f,62.4660287294f,62.5851250961f,62.7041244128f,62.8230270213f,62.9418332613f, + 63.0605434703f,63.1791579837f,63.2976771350f,63.4161012555f,63.5344306745f,63.6526657193f, + 63.7708067152f,63.8888539855f,64.0068078516f,64.1246686328f,64.2424366468f,64.3601122090f, + 64.4776956331f,64.5951872310f,64.7125873125f,64.8298961859f,64.9471141573f,65.0642415312f, + 65.1812786102f,65.2982256953f,65.4150830854f,65.5318510781f,65.6485299688f,65.7651200514f, + 65.8816216181f,65.9980349595f,66.1143603642f,66.2305981194f,66.3467485106f,66.4628118215f, + 66.5787883345f,66.6946783300f,66.8104820871f,66.9261998832f,67.0418319940f,67.1573786939f, + 67.2728402556f,67.3882169501f,67.5035090473f,67.6187168152f,67.7338405204f,67.8488804281f, + 67.9638368020f,68.0787099043f,68.1934999957f,68.3082073356f,68.4228321819f,68.5373747910f, + 68.6518354179f,68.7662143164f,68.8805117387f,68.9947279357f,69.1088631570f,69.2229176507f, + 69.3368916637f,69.4507854415f,69.5645992283f,69.6783332670f,69.7919877992f,69.9055630651f, + 70.0190593037f,70.1324767528f,70.2458156488f,70.3590762270f,70.4722587213f,70.5853633645f, + 70.6983903880f,70.8113400223f,70.9242124963f,71.0370080380f,71.1497268740f,71.2623692300f, + 71.3749353302f,71.4874253979f,71.5998396551f,71.7121783227f,71.8244416205f,71.9366297670f, + 72.0487429799f,72.1607814755f,72.2727454691f,72.3846351749f,72.4964508061f,72.6081925746f, + 72.7198606915f,72.8314553666f,72.9429768088f,73.0544252259f,73.1658008247f,73.2771038108f, + 73.3883343890f,73.4994927630f,73.6105791353f,73.7215937078f,73.8325366810f,73.9434082546f, + 74.0542086273f,74.1649379969f,74.2755965600f,74.3861845126f,74.4967020493f,74.6071493641f, + 74.7175266499f,74.8278340987f,74.9380719016f,75.0482402488f,75.1583393294f,75.2683693318f, + 75.3783304435f,75.4882228508f,75.5980467396f,75.7078022944f,75.8174896992f,75.9271091370f, + 76.0366607899f,76.1461448391f,76.2555614651f,76.3649108474f,76.4741931647f,76.5834085949f, + 76.6925573150f,76.8016395013f,76.9106553292f,77.0196049731f,77.1284886069f,77.2373064036f, + 77.3460585353f,77.4547451733f,77.5633664883f,77.6719226499f,77.7804138273f,77.8888401887f, + 77.9972019015f,78.1054991325f,78.2137320475f,78.3219008118f,78.4300055898f,78.5380465452f, + 78.6460238410f,78.7539376394f,78.8617881018f,78.9695753891f,79.0772996613f,79.1849610777f, + 79.2925597969f,79.4000959769f,79.5075697749f,79.6149813473f,79.7223308501f,79.8296184382f, + 79.9368442662f,80.0440084879f,80.1511112563f,80.2581527238f,80.3651330422f,80.4720523625f, + 80.5789108353f,80.6857086103f,80.7924458365f,80.8991226625f,81.0057392361f,81.1122957045f, + 81.2187922142f,81.3252289112f,81.4316059408f,81.5379234477f,81.6441815758f,81.7503804687f, + 81.8565202691f,81.9626011194f,82.0686231610f,82.1745865351f,82.2804913820f,82.3863378415f, + 82.4921260530f,82.5978561550f,82.7035282856f,82.8091425824f,82.9146991821f,83.0201982212f, + 83.1256398354f,83.2310241599f,83.3363513293f,83.4416214778f,83.5468347389f,83.6519912455f, + 83.7570911301f,83.8621345245f,83.9671215601f,84.0720523677f,84.1769270775f,84.2817458193f, + 84.3865087223f,84.4912159151f,84.5958675259f,84.7004636823f,84.8050045115f,84.9094901400f, + 85.0139206939f,85.1182962987f,85.2226170797f,85.3268831613f,85.4310946675f,85.5352517220f, + 85.6393544479f,85.7434029677f,85.8473974035f,85.9513378769f,86.0552245091f,86.1590574207f, + 86.2628367319f,86.3665625623f,86.4702350312f,86.5738542574f,86.6774203591f,86.7809334542f, + 86.8843936600f,86.9878010934f,87.0911558709f,87.1944581085f,87.2977079216f,87.4009054254f, + 87.5040507346f,87.6071439633f,87.7101852253f,87.8131746340f,87.9161123021f,88.0189983422f, + 88.1218328663f,88.2246159860f,88.3273478124f,88.4300284562f,88.5326580279f,88.6352366372f, + 88.7377643937f,88.8402414064f,88.9426677840f,89.0450436347f,89.1473690663f,89.2496441863f, + 89.3518691018f,89.4540439192f,89.5561687448f,89.6582436845f,89.7602688437f,89.8622443273f, + 89.9641702401f,90.0660466863f,90.1678737697f,90.2696515939f,90.3713802619f,90.4730598764f, + 90.5746905398f,90.6762723540f,90.7778054207f,90.8792898409f,90.9807257156f,91.0821131453f, + 91.1834522300f,91.2847430694f,91.3859857630f,91.4871804097f,91.5883271082f,91.6894259568f, + 91.7904770534f,91.8914804956f,91.9924363807f,92.0933448055f,92.1942058666f,92.2950196600f, + 92.3957862818f,92.4965058274f,92.5971783918f,92.6978040701f,92.7983829565f,92.8989151453f, + 92.9994007303f,93.0998398050f,93.2002324624f,93.3005787954f,93.4008788965f,93.5011328579f, + 93.6013407713f,93.7015027284f,93.8016188202f,93.9016891376f,94.0017137713f,94.1016928114f, + 94.2016263479f,94.3015144703f,94.4013572681f,94.5011548300f,94.6009072449f,94.7006146011f, + 94.8002769867f,94.8998944894f,94.9994671967f,95.0989951957f,95.1984785734f,95.2979174161f, + 95.3973118103f,95.4966618419f,95.5959675966f,95.6952291597f,95.7944466164f,95.8936200514f, + 95.9927495492f,96.0918351941f,96.1908770701f,96.2898752607f,96.3888298493f,96.4877409192f, + 96.5866085529f,96.6854328332f,96.7842138422f,96.8829516620f,96.9816463742f,97.0802980603f, + 97.1789068014f,97.2774726785f,97.3759957722f,97.4744761627f,97.5729139303f,97.6713091547f, + 97.7696619155f,97.8679722920f,97.9662403632f,98.0644662079f,98.1626499046f,98.2607915316f, + 98.3588911669f,98.4569488882f,98.5549647731f,98.6529388986f,98.7508713419f,98.8487621797f, + 98.9466114885f,99.0444193444f,99.1421858235f,99.2399110016f,99.3375949541f,99.4352377563f, + 99.5328394832f,99.6304002096f,99.7279200099f,99.8253989586f,99.9228371296f,100.0202345968f, + 100.1175914337f,100.2149077137f,100.3121835099f,100.4094188952f,100.5066139422f,100.6037687234f, + 100.7008833108f,100.7979577766f,100.8949921924f,100.9919866297f,101.0889411597f,101.1858558537f, + 101.2827307823f,101.3795660162f,101.4763616257f,101.5731176811f,101.6698342523f,101.7665114090f, + 101.8631492207f,101.9597477567f,102.0563070861f,102.1528272777f,102.2493084002f,102.3457505221f, + 102.4421537115f,102.5385180365f,102.6348435649f,102.7311303643f,102.8273785020f,102.9235880452f, + 103.0197590609f,103.1158916160f,103.2119857768f,103.3080416099f,103.4040591813f,103.5000385570f, + 103.5959798028f,103.6918829842f,103.7877481666f,103.8835754151f,103.9793647947f,104.0751163702f, + 104.1708302060f,104.2665063667f,104.3621449164f,104.4577459190f,104.5533094383f,104.6488355380f, + 104.7443242815f,104.8397757320f,104.9351899524f,105.0305670058f,105.1259069546f,105.2212098614f, + 105.3164757885f,105.4117047980f,105.5068969518f,105.6020523115f,105.6971709388f,105.7922528951f, + 105.8872982415f,105.9823070389f,106.0772793484f,106.1722152304f,106.2671147454f,106.3619779539f, + 106.4568049157f,106.5515956910f,106.6463503394f,106.7410689206f,106.8357514940f,106.9303981188f, + 107.0250088540f,107.1195837586f,107.2141228914f,107.3086263108f,107.4030940752f,107.4975262429f, + 107.5919228720f,107.6862840202f,107.7806097454f,107.8749001050f,107.9691551565f,108.0633749571f, + 108.1575595639f,108.2517090336f,108.3458234232f,108.4399027890f,108.5339471877f,108.6279566753f, + 108.7219313080f,108.8158711417f,108.9097762323f,109.0036466352f,109.0974824060f,109.1912835999f, + 109.2850502721f,109.3787824776f,109.4724802713f,109.5661437077f,109.6597728415f,109.7533677269f, + 109.8469284183f,109.9404549697f,110.0339474349f,110.1274058679f,110.2208303221f,110.3142208510f, + 110.4075775081f,110.5009003464f,110.5941894189f,110.6874447786f,110.7806664782f,110.8738545702f, + 110.9670091071f,111.0601301412f,111.1532177247f,111.2462719095f,111.3392927475f,111.4322802905f, + 111.5252345899f,111.6181556974f,111.7110436640f,111.8038985411f,111.8967203797f,111.9895092305f, + 112.0822651444f,112.1749881720f,112.2676783636f,112.3603357697f,112.4529604405f,112.5455524260f, + 112.6381117761f,112.7306385406f,112.8231327691f,112.9155945113f,113.0080238164f,113.1004207337f, + 113.1927853124f,113.2851176014f,113.3774176496f,113.4696855057f,113.5619212183f,113.6541248359f, + 113.7462964068f,113.8384359793f,113.9305436014f,114.0226193210f,114.1146631860f,114.2066752442f, + 114.2986555430f,114.3906041299f,114.4825210522f,114.5744063572f,114.6662600919f,114.7580823033f, + 114.8498730381f,114.9416323432f,115.0333602650f,115.1250568500f,115.2167221447f,115.3083561951f, + 115.3999590474f,115.4915307475f,115.5830713414f,115.6745808747f,115.7660593931f,115.8575069420f, + 115.9489235669f,116.0403093131f,116.1316642256f,116.2229883494f,116.3142817296f,116.4055444109f, + 116.4967764380f,116.5879778554f,116.6791487077f,116.7702890390f,116.8613988938f,116.9524783161f, + 117.0435273498f,117.1345460389f,117.2255344272f,117.3164925583f,117.4074204757f,117.4983182229f, + 117.5891858433f,117.6800233801f,117.7708308763f,117.8616083750f,117.9523559190f,118.0430735512f, + 118.1337613143f,118.2244192507f,118.3150474030f,118.4056458135f,118.4962145245f,118.5867535781f, + 118.6772630163f,118.7677428811f,118.8581932143f,118.9486140577f,119.0390054528f,119.1293674411f, + 119.2197000642f,119.3100033632f,119.4002773794f,119.4905221539f,119.5807377277f,119.6709241416f, + 119.7610814366f,119.8512096532f,119.9413088322f,120.0313790139f,120.1214202388f,120.2114325471f, + 120.3014159792f,120.3913705750f,120.4812963746f,120.5711934178f,120.6610617446f,120.7509013945f, + 120.8407124072f,120.9304948223f,121.0202486791f,121.1099740169f,121.1996708750f,121.2893392925f, + 121.3789793085f,121.4685909619f,121.5581742915f,121.6477293361f,121.7372561343f,121.8267547248f, + 121.9162251459f,122.0056674361f,122.0950816337f,122.1844677768f,122.2738259035f,122.3631560520f, + 122.4524582600f,122.5417325654f,122.6309790060f,122.7201976194f,122.8093884431f,122.8985515147f, + 122.9876868715f,123.0767945508f,123.1658745898f,123.2549270256f,123.3439518953f,123.4329492357f, + 123.5219190838f,123.6108614763f,123.6997764499f,123.7886640412f,123.8775242866f,123.9663572226f, + 124.0551628856f,124.1439413117f,124.2326925372f,124.3214165980f,124.4101135303f,124.4987833699f, + 124.5874261526f,124.6760419142f,124.7646306903f,124.8531925165f,124.9417274284f,125.0302354612f, + 125.1187166504f,125.2071710311f,125.2955986386f,125.3839995079f,125.4723736740f,125.5607211719f, + 125.6490420363f,125.7373363021f,125.8256040038f,125.9138451762f,126.0020598537f,126.0902480708f, + 126.1784098618f,126.2665452609f,126.3546543025f,126.4427370206f,126.5307934493f,126.6188236225f, + 126.7068275742f,126.7948053381f,126.8827569480f,126.9706824375f,127.0585818402f,127.1464551896f, + 127.2343025192f,127.3221238623f,127.4099192522f,127.4976887221f,127.5854323050f,127.6731500342f, + 127.7608419425f,127.8485080628f,127.9361484281f,128.0237630709f,128.1113520241f,128.1989153202f, + 128.2864529918f,128.3739650713f,128.4614515912f,128.5489125836f,128.6363480810f,128.7237581154f, + 128.8111427189f,128.8985019236f,128.9858357615f,129.0731442644f,129.1604274642f,129.2476853926f, + 129.3349180812f,129.4221255617f,129.5093078657f,129.5964650245f,129.6835970696f,129.7707040323f, + 129.8577859439f,129.9448428355f,130.0318747383f,130.1188816833f,130.2058637015f,130.2928208239f, + 130.3797530812f,130.4666605043f,130.5535431239f,130.6404009705f,130.7272340749f,130.8140424675f, + 130.9008261787f,130.9875852390f,131.0743196786f,131.1610295278f,131.2477148168f,131.3343755757f, + 131.4210118345f,131.5076236233f,131.5942109719f,131.6807739103f,131.7673124681f,131.8538266752f, + 131.9403165612f,132.0267821556f,132.1132234881f,132.1996405881f,132.2860334850f,132.3724022082f, + 132.4587467869f,132.5450672504f,132.6313636279f,132.7176359483f,132.8038842408f,132.8901085343f, + 132.9763088578f,133.0624852401f,133.1486377099f,133.2347662960f,133.3208710271f,133.4069519318f, + 133.4930090386f,133.5790423760f,133.6650519724f,133.7510378562f,133.8370000556f,133.9229385990f, + 134.0088535146f,134.0947448303f,134.1806125744f,134.2664567748f,134.3522774594f,134.4380746562f, + 134.5238483930f,134.6095986975f,134.6953255975f,134.7810291206f,134.8667092944f,134.9523661465f, + 135.0379997043f,135.1236099953f,135.2091970468f,135.2947608862f,135.3803015407f,135.4658190375f, + 135.5513134037f,135.6367846665f,135.7222328529f,135.8076579897f,135.8930601041f,135.9784392227f, + 136.0637953725f,136.1491285802f,136.2344388724f,136.3197262759f,136.4049908171f,136.4902325227f, + 136.5754514192f,136.6606475328f,136.7458208901f,136.8309715173f,136.9160994407f,137.0012046865f, + 137.0862872808f,137.1713472498f,137.2563846195f,137.3413994159f,137.4263916650f,137.5113613926f, + 137.5963086245f,137.6812333866f,137.7661357046f,137.8510156042f,137.9358731110f,138.0207082505f, + 138.1055210484f,138.1903115300f,138.2750797208f,138.3598256462f,138.4445493315f,138.5292508020f, + 138.6139300828f,138.6985871992f,138.7832221762f,138.8678350390f,138.9524258125f,139.0369945218f, + 139.1215411916f,139.2060658470f,139.2905685127f,139.3750492135f,139.4595079741f,139.5439448191f, + 139.6283597733f,139.7127528612f,139.7971241072f,139.8814735359f,139.9658011717f,140.0501070390f, + 140.1343911620f,140.2186535652f,140.3028942727f,140.3871133086f,140.4713106973f,140.5554864626f, + 140.6396406287f,140.7237732196f,140.8078842593f,140.8919737715f,140.9760417802f,141.0600883092f, + 141.1441133823f,141.2281170231f,141.3120992554f,141.3960601027f,141.4799995886f,141.5639177367f, + 141.6478145705f,141.7316901133f,141.8155443886f,141.8993774198f,141.9831892301f,142.0669798427f, + 142.1507492810f,142.2344975681f,142.3182247270f,142.4019307809f,142.4856157528f,142.5692796657f, + 142.6529225426f,142.7365444062f,142.8201452796f,142.9037251855f,142.9872841467f,143.0708221858f, + 143.1543393257f,143.2378355888f,143.3213109979f,143.4047655755f,143.4881993440f,143.5716123260f, + 143.6550045438f,143.7383760199f,143.8217267766f,143.9050568361f,143.9883662208f,144.0716549528f, + 144.1549230543f,144.2381705475f,144.3213974544f,144.4046037970f,144.4877895974f,144.5709548775f, + 144.6540996592f,144.7372239644f,144.8203278150f,144.9034112327f,144.9864742394f,145.0695168566f, + 145.1525391061f,145.2355410094f,145.3185225883f,145.4014838642f,145.4844248587f,145.5673455931f, + 145.6502460891f,145.7331263678f,145.8159864507f,145.8988263591f,145.9816461143f,146.0644457375f, + 146.1472252498f,146.2299846724f,146.3127240265f,146.3954433331f,146.4781426132f,146.5608218878f, + 146.6434811779f,146.7261205044f,146.8087398882f,146.8913393501f,146.9739189109f,147.0564785914f, + 147.1390184123f,147.2215383942f,147.3040385579f,147.3865189239f,147.4689795129f,147.5514203452f, + 147.6338414415f,147.7162428222f,147.7986245077f,147.8809865184f,147.9633288746f,148.0456515967f, + 148.1279547049f,148.2102382194f,148.2925021605f,148.3747465483f,148.4569714029f,148.5391767445f, + 148.6213625930f,148.7035289685f,148.7856758910f,148.8678033804f,148.9499114566f,149.0320001395f, + 149.1140694489f,149.1961194046f,149.2781500263f,149.3601613339f,149.4421533470f,149.5241260852f, + 149.6060795682f,149.6880138155f,149.7699288467f,149.8518246813f,149.9337013388f,150.0155588387f, + 150.0973972003f,150.1792164430f,150.2610165862f,150.3427976491f,150.4245596511f,150.5063026114f, + 150.5880265491f,150.6697314835f,150.7514174336f,150.8330844186f,150.9147324576f,150.9963615695f, + 151.0779717734f,151.1595630882f,151.2411355329f,151.3226891264f,151.4042238874f,151.4857398350f, + 151.5672369878f,151.6487153646f,151.7301749841f,151.8116158651f,151.8930380262f,151.9744414860f, + 152.0558262632f,152.1371923763f,152.2185398438f,152.2998686843f,152.3811789161f,152.4624705578f, + 152.5437436278f,152.6249981444f,152.7062341259f,152.7874515908f,152.8686505571f,152.9498310433f, + 153.0309930674f,153.1121366477f,153.1932618024f,153.2743685495f,153.3554569071f,153.4365268933f, + 153.5175785261f,153.5986118235f,153.6796268035f,153.7606234840f,153.8416018828f,153.9225620179f, + 154.0035039071f,154.0844275682f,154.1653330190f,154.2462202772f,154.3270893605f,154.4079402866f, + 154.4887730732f,154.5695877379f,154.6503842982f,154.7311627718f,154.8119231762f,154.8926655288f, + 154.9733898472f,155.0540961487f,155.1347844509f,155.2154547711f,155.2961071266f,155.3767415347f, + 155.4573580128f,155.5379565781f,155.6185372479f,155.6991000393f,155.7796449696f,155.8601720558f, + 155.9406813151f,156.0211727646f,156.1016464214f,156.1821023024f,156.2625404247f,156.3429608053f, + 156.4233634611f,156.5037484089f,156.5841156658f,156.6644652485f,156.7447971740f,156.8251114589f, + 156.9054081201f,156.9856871743f,157.0659486382f,157.1461925285f,157.2264188620f,157.3066276551f, + 157.3868189246f,157.4669926869f,157.5471489587f,157.6272877565f,157.7074090968f,157.7875129959f, + 157.8675994705f,157.9476685368f,158.0277202114f,158.1077545104f,158.1877714503f,158.2677710474f, + 158.3477533179f,158.4277182781f,158.5076659443f,158.5875963325f,158.6675094590f,158.7474053400f, + 158.8272839914f,158.9071454295f,158.9869896703f,159.0668167298f,159.1466266241f,159.2264193691f, + 159.3061949807f,159.3859534750f,159.4656948677f,159.5454191749f,159.6251264123f,159.7048165957f, + 159.7844897410f,159.8641458640f,159.9437849804f,160.0234071058f,160.1030122561f,160.1826004468f, + 160.2621716937f,160.3417260124f,160.4212634183f,160.5007839272f,160.5802875545f,160.6597743159f, + 160.7392442266f,160.8186973023f,160.8981335584f,160.9775530103f,161.0569556734f,161.1363415631f, + 161.2157106946f,161.2950630834f,161.3743987447f,161.4537176937f,161.5330199458f,161.6123055162f, + 161.6915744200f,161.7708266724f,161.8500622886f,161.9292812837f,162.0084836727f,162.0876694708f, + 162.1668386930f,162.2459913544f,162.3251274698f,162.4042470544f,162.4833501231f,162.5624366907f, + 162.6415067723f,162.7205603826f,162.7995975366f,162.8786182490f,162.9576225347f,163.0366104085f, + 163.1155818851f,163.1945369793f,163.2734757058f,163.3523980793f,163.4313041144f,163.5101938259f, + 163.5890672282f,163.6679243361f,163.7467651641f,163.8255897267f,163.9043980385f,163.9831901141f, + 164.0619659678f,164.1407256141f,164.2194690675f,164.2981963425f,164.3769074533f,164.4556024144f, + 164.5342812400f,164.6129439447f,164.6915905426f,164.7702210480f,164.8488354752f,164.9274338384f, + 165.0060161518f,165.0845824297f,165.1631326862f,165.2416669354f,165.3201851915f,165.3986874686f, + 165.4771737807f,165.5556441419f,165.6340985663f,165.7125370679f,165.7909596606f,165.8693663585f, + 165.9477571755f,166.0261321255f,166.1044912224f,166.1828344801f,166.2611619125f,166.3394735334f, + 166.4177693567f,166.4960493961f,166.5743136654f,166.6525621784f,166.7307949488f,166.8090119903f, + 166.8872133167f,166.9653989416f,167.0435688786f,167.1217231414f,167.1998617436f,167.2779846988f, + 167.3560920205f,167.4341837223f,167.5122598177f,167.5903203203f,167.6683652435f,167.7463946007f, + 167.8244084055f,167.9024066713f,167.9803894113f,168.0583566392f,168.1363083681f,168.2142446114f, + 168.2921653825f,168.3700706947f,168.4479605612f,168.5258349954f,168.6036940103f,168.6815376194f, + 168.7593658357f,168.8371786724f,168.9149761428f,168.9927582599f,169.0705250369f,169.1482764869f, + 169.2260126229f,169.3037334580f,169.3814390053f,169.4591292777f,169.5368042884f,169.6144640502f, + 169.6921085762f,169.7697378792f,169.8473519722f,169.9249508681f,170.0025345798f,170.0801031202f, + 170.1576565021f,170.2351947383f,170.3127178416f,170.3902258249f,170.4677187009f,170.5451964824f, + 170.6226591820f,170.7001068125f,170.7775393866f,170.8549569170f,170.9323594164f,171.0097468972f, + 171.0871193723f,171.1644768542f,171.2418193554f,171.3191468886f,171.3964594662f,171.4737571009f, + 171.5510398050f,171.6283075911f,171.7055604718f,171.7827984593f,171.8600215662f,171.9372298048f, + 172.0144231877f,172.0916017270f,172.1687654353f,172.2459143248f,172.3230484079f,172.4001676969f, + 172.4772722040f,172.5543619416f,172.6314369219f,172.7084971570f,172.7855426594f,172.8625734410f, + 172.9395895142f,173.0165908910f,173.0935775837f,173.1705496043f,173.2475069650f,173.3244496778f, + 173.4013777548f,173.4782912082f,173.5551900498f,173.6320742918f,173.7089439461f,173.7857990248f, + 173.8626395397f,173.9394655029f,174.0162769263f,174.0930738218f,174.1698562013f,174.2466240766f, + 174.3233774598f,174.4001163625f,174.4768407966f,174.5535507740f,174.6302463064f,174.7069274056f, + 174.7835940834f,174.8602463515f,174.9368842217f,175.0135077056f,175.0901168150f,175.1667115616f, + 175.2432919569f,175.3198580127f,175.3964097406f,175.4729471521f,175.5494702589f,175.6259790726f, + 175.7024736047f,175.7789538667f,175.8554198703f,175.9318716268f,176.0083091478f,176.0847324449f, + 176.1611415293f,176.2375364127f,176.3139171063f,176.3902836217f,176.4666359702f,176.5429741633f, + 176.6192982122f,176.6956081283f,176.7719039230f,176.8481856076f,176.9244531933f,177.0007066915f, + 177.0769461134f,177.1531714704f,177.2293827735f,177.3055800341f,177.3817632633f,177.4579324723f, + 177.5340876724f,177.6102288746f,177.6863560902f,177.7624693302f,177.8385686057f,177.9146539279f, + 177.9907253078f,178.0667827565f,178.1428262850f,178.2188559044f,178.2948716257f,178.3708734598f, + 178.4468614179f,178.5228355108f,178.5987957495f,178.6747421449f,178.7506747080f,178.8265934497f, + 178.9024983809f,178.9783895124f,179.0542668552f,179.1301304200f,179.2059802178f,179.2818162592f, + 179.3576385552f,179.4334471165f,179.5092419539f,179.5850230782f,179.6607905000f,179.7365442302f, + 179.8122842794f,179.8880106584f,179.9637233778f,180.0394224483f,180.1151078806f,180.1907796852f, + 180.2664378729f,180.3420824542f,180.4177134398f,180.4933308401f,180.5689346659f,180.6445249276f, + 180.7201016357f,180.7956648009f,180.8712144335f,180.9467505442f,181.0222731434f,181.0977822415f, + 181.1732778490f,181.2487599764f,181.3242286340f,181.3996838323f,181.4751255817f,181.5505538925f, + 181.6259687752f,181.7013702400f,181.7767582973f,181.8521329575f,181.9274942309f,182.0028421276f, + 182.0781766581f,182.1534978326f,182.2288056614f,182.3041001546f,182.3793813226f,182.4546491755f, + 182.5299037235f,182.6051449769f,182.6803729457f,182.7555876402f,182.8307890705f,182.9059772467f, + 182.9811521789f,183.0563138773f,183.1314623519f,183.2065976128f,183.2817196701f,183.3568285338f, + 183.4319242139f,183.5070067206f,183.5820760637f,183.6571322532f,183.7321752993f,183.8072052118f, + 183.8822220006f,183.9572256758f,184.0322162472f,184.1071937248f,184.1821581184f,184.2571094380f, + 184.3320476935f,184.4069728946f,184.4818850513f,184.5567841733f,184.6316702706f,184.7065433528f, + 184.7814034299f,184.8562505115f,184.9310846075f,185.0059057276f,185.0807138816f,185.1555090791f, + 185.2302913300f,185.3050606438f,185.3798170304f,185.4545604994f,185.5292910604f,185.6040087231f, + 185.6787134972f,185.7534053922f,185.8280844179f,185.9027505837f,185.9774038993f,186.0520443743f, + 186.1266720182f,186.2012868406f,186.2758888510f,186.3504780590f,186.4250544740f,186.4996181056f, + 186.5741689633f,186.6487070564f,186.7232323946f,186.7977449873f,186.8722448438f,186.9467319736f, + 187.0212063862f,187.0956680910f,187.1701170972f,187.2445534144f,187.3189770518f,187.3933880189f, + 187.4677863249f,187.5421719793f,187.6165449912f,187.6909053701f,187.7652531252f,187.8395882658f, + 187.9139108012f,187.9882207406f,188.0625180933f,188.1368028685f,188.2110750754f,188.2853347233f, + 188.3595818212f,188.4338163785f,188.5080384043f,188.5822479078f,188.6564448980f,188.7306293842f, + 188.8048013755f,188.8789608809f,188.9531079096f,189.0272424707f,189.1013645733f,189.1754742264f, + 189.2495714391f,189.3236562204f,189.3977285793f,189.4717885249f,189.5458360662f,189.6198712122f, + 189.6938939719f,189.7679043542f,189.8419023681f,189.9158880226f,189.9898613266f,190.0638222890f, + 190.1377709188f,190.2117072248f,190.2856312160f,190.3595429012f,190.4334422893f,190.5073293892f, + 190.5812042097f,190.6550667596f,190.7289170479f,190.8027550832f,190.8765808744f,190.9503944303f, + 191.0241957597f,191.0979848714f,191.1717617740f,191.2455264764f,191.3192789873f,191.3930193154f, + 191.4667474695f,191.5404634582f,191.6141672902f,191.6878589743f,191.7615385190f,191.8352059331f, + 191.9088612252f,191.9825044039f,192.0561354779f,192.1297544558f,192.2033613461f,192.2769561576f, + 192.3505388987f,192.4241095781f,192.4976682043f,192.5712147858f,192.6447493312f,192.7182718491f, + 192.7917823479f,192.8652808362f,192.9387673224f,193.0122418151f,193.0857043227f,193.1591548537f, + 193.2325934166f,193.3060200197f,193.3794346716f,193.4528373806f,193.5262281552f,193.5996070038f, + 193.6729739347f,193.7463289564f,193.8196720772f,193.8930033056f,193.9663226497f,194.0396301180f, + 194.1129257188f,194.1862094605f,194.2594813513f,194.3327413995f,194.4059896135f,194.4792260014f, + 194.5524505717f,194.6256633325f,194.6988642921f,194.7720534586f,194.8452308405f,194.9183964458f, + 194.9915502828f,195.0646923597f,195.1378226846f,195.2109412658f,195.2840481114f,195.3571432296f, + 195.4302266285f,195.5032983162f,195.5763583010f,195.6494065908f,195.7224431938f,195.7954681182f, + 195.8684813719f,195.9414829631f,196.0144728998f,196.0874511902f,196.1604178422f,196.2333728638f, + 196.3063162632f,196.3792480483f,196.4521682272f,196.5250768078f,196.5979737982f,196.6708592062f, + 196.7437330399f,196.8165953073f,196.8894460163f,196.9622851748f,197.0351127907f,197.1079288721f, + 197.1807334267f,197.2535264626f,197.3263079876f,197.3990780095f,197.4718365363f,197.5445835758f, + 197.6173191358f,197.6900432243f,197.7627558491f,197.8354570179f,197.9081467386f,197.9808250191f, + 198.0534918670f,198.1261472902f,198.1987912965f,198.2714238937f,198.3440450894f,198.4166548915f, + 198.4892533077f,198.5618403458f,198.6344160133f,198.7069803182f,198.7795332680f,198.8520748705f, + 198.9246051333f,198.9971240642f,199.0696316708f,199.1421279607f,199.2146129416f,199.2870866212f, + 199.3595490071f,199.4320001068f,199.5044399281f,199.5768684785f,199.6492857656f,199.7216917971f, + 199.7940865804f,199.8664701231f,199.9388424328f,200.0112035171f,200.0835533835f,200.1558920396f, + 200.2282194928f,200.3005357506f,200.3728408206f,200.4451347103f,200.5174174271f,200.5896889786f, + 200.6619493722f,200.7341986153f,200.8064367155f,200.8786636801f,200.9508795166f,201.0230842325f, + 201.0952778350f,201.1674603317f,201.2396317300f,201.3117920372f,201.3839412607f,201.4560794079f, + 201.5282064862f,201.6003225029f,201.6724274653f,201.7445213808f,201.8166042568f,201.8886761005f, + 201.9607369193f,202.0327867204f,202.1048255112f,202.1768532990f,202.2488700910f,202.3208758945f, + 202.3928707167f,202.4648545650f,202.5368274465f,202.6087893686f,202.6807403383f,202.7526803630f, + 202.8246094499f,202.8965276061f,202.9684348388f,203.0403311553f,203.1122165627f,203.1840910682f, + 203.2559546790f,203.3278074021f,203.3996492448f,203.4714802142f,203.5433003174f,203.6151095615f, + 203.6869079536f,203.7586955009f,203.8304722104f,203.9022380892f,203.9739931444f,204.0457373831f, + 204.1174708124f,204.1891934392f,204.2609052706f,204.3326063138f,204.4042965756f,204.4759760631f, + 204.5476447834f,204.6193027434f,204.6909499501f,204.7625864106f,204.8342121318f,204.9058271206f, + 204.9774313841f,205.0490249292f,205.1206077629f,205.1921798920f,205.2637413236f,205.3352920645f, + 205.4068321217f,205.4783615020f,205.5498802124f,205.6213882598f,205.6928856511f,205.7643723931f, + 205.8358484927f,205.9073139568f,205.9787687921f,206.0502130057f,206.1216466042f,206.1930695946f, + 206.2644819837f,206.3358837782f,206.4072749850f,206.4786556109f,206.5500256627f,206.6213851471f, + 206.6927340710f,206.7640724411f,206.8354002641f,206.9067175469f,206.9780242961f,207.0493205185f, + 207.1206062209f,207.1918814099f,207.2631460924f,207.3344002748f,207.4056439641f,207.4768771669f, + 207.5480998898f,207.6193121395f,207.6905139228f,207.7617052462f,207.8328861165f,207.9040565402f, + 207.9752165241f,208.0463660747f,208.1175051986f,208.1886339026f,208.2597521932f,208.3308600770f, + 208.4019575606f,208.4730446505f,208.5441213535f,208.6151876760f,208.6862436246f,208.7572892058f, + 208.8283244263f,208.8993492926f,208.9703638111f,209.0413679885f,209.1123618313f,209.1833453459f, + 209.2543185389f,209.3252814168f,209.3962339860f,209.4671762531f,209.5381082245f,209.6090299068f, + 209.6799413063f,209.7508424296f,209.8217332830f,209.8926138730f,209.9634842062f,210.0343442888f, + 210.1051941273f,210.1760337281f,210.2468630977f,210.3176822423f,210.3884911686f,210.4592898827f, + 210.5300783911f,210.6008567002f,210.6716248163f,210.7423827458f,210.8131304950f,210.8838680703f, + 210.9545954780f,211.0253127245f,211.0960198160f,211.1667167589f,211.2374035595f,211.3080802241f, + 211.3787467590f,211.4494031705f,211.5200494648f,211.5906856483f,211.6613117272f,211.7319277077f, + 211.8025335962f,211.8731293988f,211.9437151218f,212.0142907715f,212.0848563540f,212.1554118756f, + 212.2259573425f,212.2964927609f,212.3670181371f,212.4375334771f,212.5080387872f,212.5785340735f, + 212.6490193423f,212.7194945997f,212.7899598518f,212.8604151049f,212.9308603650f,213.0012956383f, + 213.0717209309f,213.1421362490f,213.2125415986f,213.2829369860f,213.3533224171f,213.4236978981f, + 213.4940634351f,213.5644190342f,213.6347647014f,213.7051004428f,213.7754262645f,213.8457421725f, + 213.9160481730f,213.9863442719f,214.0566304753f,214.1269067892f,214.1971732196f,214.2674297726f, + 214.3376764542f,214.4079132703f,214.4781402271f,214.5483573304f,214.6185645863f,214.6887620007f, + 214.7589495797f,214.8291273292f,214.8992952551f,214.9694533635f,215.0396016602f,215.1097401512f, + 215.1798688424f,215.2499877399f,215.3200968494f,215.3901961769f,215.4602857284f,215.5303655097f, + 215.6004355267f,215.6704957854f,215.7405462915f,215.8105870511f,215.8806180699f,215.9506393539f, + 216.0206509088f,216.0906527406f,216.1606448551f,216.2306272581f,216.3005999555f,216.3705629531f, + 216.4405162568f,216.5104598722f,216.5803938054f,216.6503180620f,216.7202326479f,216.7901375688f, + 216.8600328306f,216.9299184390f,216.9997943998f,217.0696607187f,217.1395174016f,217.2093644542f, + 217.2792018822f,217.3490296914f,217.4188478875f,217.4886564763f,217.5584554635f,217.6282448547f, + 217.6980246558f,217.7677948724f,217.8375555102f,217.9073065749f,217.9770480722f,218.0467800078f, + 218.1165023874f,218.1862152167f,218.2559185012f,218.3256122467f,218.3952964589f,218.4649711433f, + 218.5346363056f,218.6042919515f,218.6739380866f,218.7435747164f,218.8132018467f,218.8828194830f, + 218.9524276310f,219.0220262962f,219.0916154843f,219.1611952008f,219.2307654513f,219.3003262414f, + 219.3698775767f,219.4394194627f,219.5089519050f,219.5784749091f,219.6479884807f,219.7174926251f, + 219.7869873481f,219.8564726551f,219.9259485516f,219.9954150431f,220.0648721352f,220.1343198334f, + 220.2037581432f,220.2731870701f,220.3426066195f,220.4120167970f,220.4814176080f,220.5508090580f, + 220.6201911525f,220.6895638970f,220.7589272968f,220.8282813576f,220.8976260846f,220.9669614833f, + 221.0362875592f,221.1056043178f,221.1749117643f,221.2442099043f,221.3134987432f,221.3827782863f, + 221.4520485391f,221.5213095070f,221.5905611953f,221.6598036095f,221.7290367548f,221.7982606368f, + 221.8674752608f,221.9366806320f,222.0058767560f,222.0750636380f,222.1442412833f,222.2134096974f, + 222.2825688856f,222.3517188531f,222.4208596054f,222.4899911477f,222.5591134853f,222.6282266236f, + 222.6973305678f,222.7664253233f,222.8355108954f,222.9045872893f,222.9736545103f,223.0427125637f, + 223.1117614548f,223.1808011888f,223.2498317709f,223.3188532066f,223.3878655009f,223.4568686591f, + 223.5258626866f,223.5948475884f,223.6638233699f,223.7327900362f,223.8017475926f,223.8706960443f, + 223.9396353966f,224.0085656545f,224.0774868232f,224.1463989081f,224.2153019143f,224.2841958469f, + 224.3530807111f,224.4219565121f,224.4908232551f,224.5596809453f,224.6285295876f,224.6973691875f, + 224.7661997499f,224.8350212800f,224.9038337829f,224.9726372638f,225.0414317278f,225.1102171801f, + 225.1789936256f,225.2477610696f,225.3165195172f,225.3852689733f,225.4540094432f,225.5227409319f, + 225.5914634446f,225.6601769862f,225.7288815618f,225.7975771766f,225.8662638355f,225.9349415437f, + 226.0036103062f,226.0722701280f,226.1409210142f,226.2095629698f,226.2781959998f,226.3468201094f, + 226.4154353034f,226.4840415869f,226.5526389650f,226.6212274426f,226.6898070247f,226.7583777164f, + 226.8269395226f,226.8954924483f,226.9640364985f,227.0325716781f,227.1010979922f,227.1696154458f, + 227.2381240437f,227.3066237909f,227.3751146924f,227.4435967531f,227.5120699781f,227.5805343721f, + 227.6489899402f,227.7174366872f,227.7858746182f,227.8543037379f,227.9227240514f,227.9911355636f, + 228.0595382792f,228.1279322034f,228.1963173408f,228.2646936965f,228.3330612753f,228.4014200821f, + 228.4697701217f,228.5381113991f,228.6064439191f,228.6747676865f,228.7430827063f,228.8113889832f, + 228.8796865222f,228.9479753280f,229.0162554055f,229.0845267595f,229.1527893949f,229.2210433164f, + 229.2892885290f,229.3575250374f,229.4257528463f,229.4939719607f,229.5621823854f,229.6303841250f, + 229.6985771844f,229.7667615685f,229.8349372819f,229.9031043294f,229.9712627159f,230.0394124460f, + 230.1075535246f,230.1756859564f,230.2438097462f,230.3119248986f,230.3800314185f,230.4481293106f, + 230.5162185796f,230.5842992303f,230.6523712673f,230.7204346955f,230.7884895194f,230.8565357439f, + 230.9245733736f,230.9926024132f,231.0606228675f,231.1286347411f,231.1966380387f,231.2646327650f, + 231.3326189246f,231.4005965223f,231.4685655628f,231.5365260506f,231.6044779905f,231.6724213871f, + 231.7403562450f,231.8082825689f,231.8762003635f,231.9441096334f,232.0120103832f,232.0799026176f, + 232.1477863411f,232.2156615585f,232.2835282742f,232.3513864930f,232.4192362194f,232.4870774581f, + 232.5549102136f,232.6227344905f,232.6905502935f,232.7583576271f,232.8261564958f,232.8939469044f, + 232.9617288573f,233.0295023590f,233.0972674143f,233.1650240276f,233.2327722035f,233.3005119466f, + 233.3682432613f,233.4359661522f,233.5036806240f,233.5713866810f,233.6390843278f,233.7067735691f, + 233.7744544092f,233.8421268526f,233.9097909040f,233.9774465678f,234.0450938486f,234.1127327507f, + 234.1803632788f,234.2479854372f,234.3155992306f,234.3832046633f,234.4508017399f,234.5183904648f, + 234.5859708424f,234.6535428774f,234.7211065740f,234.7886619369f,234.8562089704f,234.9237476789f, + 234.9912780670f,235.0588001390f,235.1263138994f,235.1938193527f,235.2613165032f,235.3288053554f, + 235.3962859137f,235.4637581825f,235.5312221662f,235.5986778693f,235.6661252961f,235.7335644510f, + 235.8009953384f,235.8684179628f,235.9358323285f,236.0032384398f,236.0706363013f,236.1380259171f, + 236.2054072918f,236.2727804296f,236.3401453350f,236.4075020122f,236.4748504657f,236.5421906998f, + 236.6095227189f,236.6768465272f,236.7441621291f,236.8114695290f,236.8787687311f,236.9460597399f, + 237.0133425596f,237.0806171945f,237.1478836489f,237.2151419273f,237.2823920338f,237.3496339727f, + 237.4168677485f,237.4840933652f,237.5513108274f,237.6185201391f,237.6857213048f,237.7529143286f, + 237.8200992150f,237.8872759680f,237.9544445920f,238.0216050913f,238.0887574701f,238.1559017327f, + 238.2230378833f,238.2901659261f,238.3572858654f,238.4243977055f,238.4915014506f,238.5585971048f, + 238.6256846725f,238.6927641578f,238.7598355650f,238.8268988982f,238.8939541618f,238.9610013598f, + 239.0280404966f,239.0950715762f,239.1620946029f,239.2291095809f,239.2961165144f,239.3631154076f, + 239.4301062645f,239.4970890895f,239.5640638866f,239.6310306601f,239.6979894141f,239.7649401527f, + 239.8318828802f,239.8988176006f,239.9657443182f,240.0326630371f,240.0995737613f,240.1664764951f, + 240.2333712426f,240.3002580079f,240.3671367951f,240.4340076084f,240.5008704518f,240.5677253296f, + 240.6345722457f,240.7014112044f,240.7682422096f,240.8350652656f,240.9018803764f,240.9686875461f, + 241.0354867787f,241.1022780785f,241.1690614494f,241.2358368955f,241.3026044209f,241.3693640297f, + 241.4361157259f,241.5028595136f,241.5695953969f,241.6363233798f,241.7030434663f,241.7697556606f, + 241.8364599666f,241.9031563884f,241.9698449301f,242.0365255956f,242.1031983890f,242.1698633144f, + 242.2365203756f,242.3031695769f,242.3698109221f,242.4364444153f,242.5030700605f,242.5696878617f, + 242.6362978229f,242.7028999480f,242.7694942412f,242.8360807064f,242.9026593475f,242.9692301685f, + 243.0357931735f,243.1023483664f,243.1688957511f,243.2354353317f,243.3019671121f,243.3684910963f, + 243.4350072882f,243.5015156917f,243.5680163109f,243.6345091496f,243.7009942119f,243.7674715016f, + 243.8339410227f,243.9004027791f,243.9668567748f,244.0333030137f,244.0997414996f,244.1661722366f, + 244.2325952285f,244.2990104792f,244.3654179927f,244.4318177729f,244.4982098236f,244.5645941488f, + 244.6309707523f,244.6973396380f,244.7637008099f,244.8300542718f,244.8964000276f,244.9627380811f, + 245.0290684363f,245.0953910970f,245.1617060671f,245.2280133504f,245.2943129508f,245.3606048721f, + 245.4268891183f,245.4931656931f,245.5594346004f,245.6256958441f,245.6919494279f,245.7581953558f, + 245.8244336315f,245.8906642589f,245.9568872418f,246.0231025841f,246.0893102895f,246.1555103618f, + 246.2217028049f,246.2878876226f,246.3540648187f,246.4202343969f,246.4863963612f,246.5525507152f, + 246.6186974628f,246.6848366078f,246.7509681539f,246.8170921050f,246.8832084648f,246.9493172370f, + 247.0154184255f,247.0815120340f,247.1475980663f,247.2136765262f,247.2797474173f,247.3458107436f, + 247.4118665086f,247.4779147162f,247.5439553701f,247.6099884741f,247.6760140319f,247.7420320472f, + 247.8080425237f,247.8740454652f,247.9400408755f,248.0060287582f,248.0720091170f,248.1379819557f, + 248.2039472780f,248.2699050876f,248.3358553882f,248.4017981834f,248.4677334771f,248.5336612729f, + 248.5995815745f,248.6654943855f,248.7313997098f,248.7972975509f,248.8631879125f,248.9290707983f, + 248.9949462120f,249.0608141573f,249.1266746377f,249.1925276571f,249.2583732190f,249.3242113272f, + 249.3900419852f,249.4558651967f,249.5216809654f,249.5874892948f,249.6532901888f,249.7190836508f, + 249.7848696845f,249.8506482936f,249.9164194817f,249.9821832524f,250.0479396094f,250.1136885562f, + 250.1794300964f,250.2451642338f,250.3108909719f,250.3766103143f,250.4423222646f,250.5080268264f, + 250.5737240033f,250.6394137989f,250.7050962168f,250.7707712607f,250.8364389340f,250.9020992403f, + 250.9677521833f,251.0333977665f,251.0990359936f,251.1646668679f,251.2302903932f,251.2959065730f, + 251.3615154109f,251.4271169103f,251.4927110750f,251.5582979083f,251.6238774139f,251.6894495954f, + 251.7550144562f,251.8205719999f,251.8861222300f,251.9516651502f,252.0172007638f,252.0827290744f, + 252.1482500856f,252.2137638009f,252.2792702238f,252.3447693578f,252.4102612065f,252.4757457732f, + 252.5412230616f,252.6066930752f,252.6721558174f,252.7376112918f,252.8030595018f,252.8685004509f, + 252.9339341427f,252.9993605806f,253.0647797681f,253.1301917086f,253.1955964058f,253.2609938629f, + 253.3263840835f,253.3917670711f,253.4571428292f,253.5225113611f,253.5878726704f,253.6532267605f, + 253.7185736349f,253.7839132970f,253.8492457503f,253.9145709981f,253.9798890441f,254.0451998915f, + 254.1105035438f,254.1758000045f,254.2410892771f,254.3063713648f,254.3716462712f,254.4369139996f, + 254.5021745535f,254.5674279364f,254.6326741515f,254.6979132024f,254.7631450925f,254.8283698250f, + 254.8935874035f,254.9587978314f,255.0240011120f,255.0891972487f,255.1543862449f,255.2195681041f, + 255.2847428295f,255.3499104246f,255.4150708928f,255.4802242374f,255.5453704618f,255.6105095694f, + 255.6756415635f,255.7407664475f,255.8058842248f,255.8709948988f,255.9360984727f,256.0011949500f, + 256.0662843339f,256.1313666279f,256.1964418353f,256.2615099594f,256.3265710035f,256.3916249711f, + 256.4566718654f,256.5217116898f,256.5867444476f,256.6517701421f,256.7167887766f,256.7818003546f, + 256.8468048792f,256.9118023538f,256.9767927817f,257.0417761662f,257.1067525107f,257.1717218184f, + 257.2366840926f,257.3016393367f,257.3665875539f,257.4315287475f,257.4964629208f,257.5613900771f, + 257.6263102197f,257.6912233518f,257.7561294768f,257.8210285980f,257.8859207185f,257.9508058417f, + 258.0156839708f,258.0805551091f,258.1454192599f,258.2102764264f,258.2751266118f,258.3399698195f, + 258.4048060527f,258.4696353147f,258.5344576086f,258.5992729377f,258.6640813053f,258.7288827147f, + 258.7936771689f,258.8584646714f,258.9232452252f,258.9880188337f,259.0527855001f,259.1175452275f, + 259.1822980193f,259.2470438785f,259.3117828085f,259.3765148125f,259.4412398936f,259.5059580551f, + 259.5706693002f,259.6353736320f,259.7000710538f,259.7647615688f,259.8294451802f,259.8941218911f, + 259.9587917047f,260.0234546243f,260.0881106530f,260.1527597940f,260.2174020504f,260.2820374255f, + 260.3466659225f,260.4112875444f,260.4759022945f,260.5405101759f,260.6051111918f,260.6697053454f, + 260.7342926397f,260.7988730781f,260.8634466635f,260.9280133992f,260.9925732883f,261.0571263340f, + 261.1216725394f,261.1862119076f,261.2507444418f,261.3152701451f,261.3797890207f,261.4443010716f, + 261.5088063010f,261.5733047121f,261.6377963079f,261.7022810916f,261.7667590662f,261.8312302350f, + 261.8956946009f,261.9601521672f,262.0246029369f,262.0890469132f,262.1534840990f,262.2179144977f, + 262.2823381121f,262.3467549455f,262.4111650008f,262.4755682813f,262.5399647900f,262.6043545299f, + 262.6687375042f,262.7331137160f,262.7974831682f,262.8618458641f,262.9262018066f,262.9905509988f, + 263.0548934439f,263.1192291448f,263.1835581046f,263.2478803264f,263.3121958132f,263.3765045682f, + 263.4408065943f,263.5051018946f,263.5693904721f,263.6336723299f,263.6979474711f,263.7622158986f, + 263.8264776156f,263.8907326250f,263.9549809298f,264.0192225332f,264.0834574381f,264.1476856476f, + 264.2119071647f,264.2761219924f,264.3403301337f,264.4045315916f,264.4687263692f,264.5329144695f, + 264.5970958954f,264.6612706500f,264.7254387363f,264.7896001573f,264.8537549160f,264.9179030153f, + 264.9820444583f,265.0461792480f,265.1103073874f,265.1744288794f,265.2385437270f,265.3026519333f, + 265.3667535011f,265.4308484335f,265.4949367335f,265.5590184039f,265.6230934479f,265.6871618683f, + 265.7512236682f,265.8152788504f,265.8793274180f,265.9433693738f,266.0074047210f,266.0714334624f, + 266.1354556009f,266.1994711396f,266.2634800813f,266.3274824290f,266.3914781857f,266.4554673543f, + 266.5194499377f,266.5834259389f,266.6473953608f,266.7113582063f,266.7753144784f,266.8392641799f, + 266.9032073139f,266.9671438832f,267.0310738908f,267.0949973395f,267.1589142323f,267.2228245722f, + 267.2867283619f,267.3506256044f,267.4145163027f,267.4784004596f,267.5422780780f,267.6061491608f, + 267.6700137110f,267.7338717314f,267.7977232248f,267.8615681943f,267.9254066427f,267.9892385728f, + 268.0530639876f,268.1168828899f,268.1806952827f,268.2445011687f,268.3083005509f,268.3720934321f, + 268.4358798153f,268.4996597032f,268.5634330987f,268.6272000048f,268.6909604242f,268.7547143598f, + 268.8184618145f,268.8822027911f,268.9459372925f,269.0096653216f,269.0733868811f,269.1371019739f, + 269.2008106029f,269.2645127709f,269.3282084808f,269.3918977353f,269.4555805374f,269.5192568898f, + 269.5829267953f,269.6465902569f,269.7102472773f,269.7738978594f,269.8375420059f,269.9011797197f, + 269.9648110037f,270.0284358605f,270.0920542931f,270.1556663042f,270.2192718967f,270.2828710734f, + 270.3464638370f,270.4100501903f,270.4736301362f,270.5372036775f,270.6007708170f,270.6643315573f, + 270.7278859015f,270.7914338521f,270.8549754120f,270.9185105841f,270.9820393710f,271.0455617756f, + 271.1090778006f,271.1725874488f,271.2360907230f,271.2995876260f,271.3630781605f,271.4265623292f, + 271.4900401350f,271.5535115807f,271.6169766689f,271.6804354025f,271.7438877841f,271.8073338166f, + 271.8707735027f,271.9342068451f,271.9976338467f,272.0610545100f,272.1244688380f,272.1878768333f, + 272.2512784986f,272.3146738368f,272.3780628505f,272.4414455425f,272.5048219154f,272.5681919722f, + 272.6315557153f,272.6949131477f,272.7582642719f,272.8216090908f,272.8849476071f,272.9482798233f, + 273.0116057424f,273.0749253670f,273.1382386998f,273.2015457434f,273.2648465007f,273.3281409743f, + 273.3914291669f,273.4547110813f,273.5179867201f,273.5812560859f,273.6445191816f,273.7077760098f, + 273.7710265732f,273.8342708744f,273.8975089163f,273.9607407013f,274.0239662323f,274.0871855119f, + 274.1503985428f,274.2136053277f,274.2768058692f,274.3400001700f,274.4031882327f,274.4663700601f, + 274.5295456549f,274.5927150196f,274.6558781569f,274.7190350695f,274.7821857601f,274.8453302313f, + 274.9084684857f,274.9716005261f,275.0347263550f,275.0978459751f,275.1609593890f,275.2240665995f, + 275.2871676091f,275.3502624205f,275.4133510363f,275.4764334591f,275.5395096916f,275.6025797365f, + 275.6656435963f,275.7287012736f,275.7917527712f,275.8547980916f,275.9178372374f,275.9808702112f, + 276.0438970158f,276.1069176537f,276.1699321274f,276.2329404397f,276.2959425931f,276.3589385903f, + 276.4219284338f,276.4849121262f,276.5478896702f,276.6108610683f,276.6738263232f,276.7367854375f, + 276.7997384136f,276.8626852544f,276.9256259622f,276.9885605397f,277.0514889896f,277.1144113143f, + 277.1773275165f,277.2402375987f,277.3031415635f,277.3660394136f,277.4289311514f,277.4918167796f, + 277.5546963007f,277.6175697172f,277.6804370319f,277.7432982471f,277.8061533656f,277.8690023898f, + 277.9318453223f,277.9946821656f,278.0575129224f,278.1203375951f,278.1831561864f,278.2459686988f, + 278.3087751347f,278.3715754969f,278.4343697877f,278.4971580098f,278.5599401658f,278.6227162580f, + 278.6854862891f,278.7482502617f,278.8110081781f,278.8737600411f,278.9365058530f,278.9992456165f, + 279.0619793341f,279.1247070082f,279.1874286414f,279.2501442362f,279.3128537952f,279.3755573209f, + 279.4382548157f,279.5009462822f,279.5636317229f,279.6263111403f,279.6889845369f,279.7516519152f, + 279.8143132778f,279.8769686270f,279.9396179655f,280.0022612957f,280.0648986201f,280.1275299413f, + 280.1901552616f,280.2527745837f,280.3153879099f,280.3779952428f,280.4405965848f,280.5031919385f, + 280.5657813063f,280.6283646906f,280.6909420941f,280.7535135191f,280.8160789682f,280.8786384437f, + 280.9411919483f,281.0037394842f,281.0662810541f,281.1288166603f,281.1913463054f,281.2538699917f, + 281.3163877219f,281.3788994982f,281.4414053232f,281.5039051993f,281.5663991290f,281.6288871148f, + 281.6913691590f,281.7538452641f,281.8163154326f,281.8787796669f,281.9412379695f,282.0036903428f, + 282.0661367892f,282.1285773111f,282.1910119111f,282.2534405915f,282.3158633548f,282.3782802034f, + 282.4406911397f,282.5030961662f,282.5654952852f,282.6278884993f,282.6902758108f,282.7526572221f, + 282.8150327356f,282.8774023539f,282.9397660792f,283.0021239140f,283.0644758607f,283.1268219218f, + 283.1891620995f,283.2514963964f,283.3138248149f,283.3761473572f,283.4384640259f,283.5007748233f, + 283.5630797519f,283.6253788140f,283.6876720120f,283.7499593482f,283.8122408252f,283.8745164453f, + 283.9367862109f,283.9990501242f,284.0613081879f,284.1235604041f,284.1858067754f,284.2480473040f, + 284.3102819924f,284.3725108428f,284.4347338578f,284.4969510397f,284.5591623908f,284.6213679134f, + 284.6835676101f,284.7457614831f,284.8079495348f,284.8701317675f,284.9323081836f,284.9944787855f, + 285.0566435756f,285.1188025561f,285.1809557294f,285.2431030980f,285.3052446640f,285.3673804299f, + 285.4295103981f,285.4916345708f,285.5537529504f,285.6158655393f,285.6779723397f,285.7400733541f, + 285.8021685847f,285.8642580340f,285.9263417041f,285.9884195975f,286.0504917165f,286.1125580633f, + 286.1746186405f,286.2366734501f,286.2987224947f,286.3607657764f,286.4228032977f,286.4848350608f, + 286.5468610680f,286.6088813217f,286.6708958242f,286.7329045778f,286.7949075847f,286.8569048474f, + 286.9188963681f,286.9808821491f,287.0428621927f,287.1048365012f,287.1668050769f,287.2287679221f, + 287.2907250391f,287.3526764303f,287.4146220978f,287.4765620440f,287.5384962711f,287.6004247816f, + 287.6623475775f,287.7242646613f,287.7861760353f,287.8480817016f,287.9099816625f,287.9718759205f, + 288.0337644776f,288.0956473363f,288.1575244987f,288.2193959672f,288.2812617439f,288.3431218313f, + 288.4049762315f,288.4668249468f,288.5286679795f,288.5905053319f,288.6523370061f,288.7141630045f, + 288.7759833294f,288.8377979828f,288.8996069673f,288.9614102848f,289.0232079379f,289.0849999286f, + 289.1467862592f,289.2085669320f,289.2703419492f,289.3321113130f,289.3938750258f,289.4556330897f, + 289.5173855070f,289.5791322798f,289.6408734106f,289.7026089014f,289.7643387545f,289.8260629722f, + 289.8877815566f,289.9494945101f,290.0112018347f,290.0729035328f,290.1345996066f,290.1962900583f, + 290.2579748901f,290.3196541042f,290.3813277029f,290.4429956883f,290.5046580627f,290.5663148283f, + 290.6279659873f,290.6896115419f,290.7512514942f,290.8128858467f,290.8745146013f,290.9361377603f, + 290.9977553260f,291.0593673005f,291.1209736860f,291.1825744848f,291.2441696989f,291.3057593307f, + 291.3673433822f,291.4289218558f,291.4904947535f,291.5520620776f,291.6136238302f,291.6751800135f, + 291.7367306298f,291.7982756812f,291.8598151698f,291.9213490979f,291.9828774677f,292.0444002812f, + 292.1059175407f,292.1674292484f,292.2289354064f,292.2904360168f,292.3519310820f,292.4134206039f, + 292.4749045849f,292.5363830270f,292.5978559324f,292.6593233033f,292.7207851418f,292.7822414501f, + 292.8436922304f,292.9051374847f,292.9665772153f,293.0280114244f,293.0894401139f,293.1508632862f, + 293.2122809433f,293.2736930874f,293.3350997206f,293.3965008451f,293.4578964631f,293.5192865766f, + 293.5806711878f,293.6420502988f,293.7034239118f,293.7647920289f,293.8261546522f,293.8875117839f, + 293.9488634261f,294.0102095809f,294.0715502505f,294.1328854369f,294.1942151423f,294.2555393688f, + 294.3168581186f,294.3781713938f,294.4394791964f,294.5007815286f,294.5620783925f,294.6233697902f, + 294.6846557238f,294.7459361955f,294.8072112073f,294.8684807614f,294.9297448599f,294.9910035048f, + 295.0522566982f,295.1135044424f,295.1747467393f,295.2359835911f,295.2972149999f,295.3584409677f, + 295.4196614966f,295.4808765889f,295.5420862464f,295.6032904714f,295.6644892659f,295.7256826321f, + 295.7868705719f,295.8480530875f,295.9092301809f,295.9704018543f,296.0315681097f,296.0927289493f, + 296.1538843749f,296.2150343889f,296.2761789932f,296.3373181899f,296.3984519810f,296.4595803687f, + 296.5207033550f,296.5818209420f,296.6429331318f,296.7040399264f,296.7651413278f,296.8262373382f, + 296.8873279595f,296.9484131940f,297.0094930435f,297.0705675102f,297.1316365962f,297.1927003034f, + 297.2537586339f,297.3148115899f,297.3758591732f,297.4369013861f,297.4979382305f,297.5589697084f, + 297.6199958220f,297.6810165732f,297.7420319642f,297.8030419968f,297.8640466733f,297.9250459955f, + 297.9860399657f,298.0470285857f,298.1080118576f,298.1689897835f,298.2299623653f,298.2909296052f, + 298.3518915051f,298.4128480670f,298.4737992931f,298.5347451853f,298.5956857456f,298.6566209760f, + 298.7175508787f,298.7784754555f,298.8393947085f,298.9003086398f,298.9612172513f,299.0221205450f, + 299.0830185230f,299.1439111873f,299.2047985399f,299.2656805827f,299.3265573178f,299.3874287473f, + 299.4482948730f,299.5091556971f,299.5700112214f,299.6308614481f,299.6917063790f,299.7525460163f, + 299.8133803618f,299.8742094177f,299.9350331858f,299.9958516682f,300.0566648668f,300.1174727837f, + 300.1782754208f,300.2390727802f,300.2998648637f,300.3606516735f,300.4214332114f,300.4822094795f, + 300.5429804797f,300.6037462140f,300.6645066844f,300.7252618928f,300.7860118413f,300.8467565318f, + 300.9074959663f,300.9682301468f,301.0289590751f,301.0896827534f,301.1504011835f,301.2111143674f, + 301.2718223071f,301.3325250046f,301.3932224617f,301.4539146805f,301.5146016630f,301.5752834110f, + 301.6359599265f,301.6966312116f,301.7572972681f,301.8179580980f,301.8786137032f,301.9392640857f, + 301.9999092475f,302.0605491905f,302.1211839166f,302.1818134278f,302.2424377260f,302.3030568132f, + 302.3636706913f,302.4242793622f,302.4848828279f,302.5454810904f,302.6060741514f,302.6666620131f, + 302.7272446773f,302.7878221460f,302.8483944210f,302.9089615044f,302.9695233980f,303.0300801037f, + 303.0906316236f,303.1511779595f,303.2117191133f,303.2722550870f,303.3327858824f,303.3933115016f, + 303.4538319463f,303.5143472186f,303.5748573204f,303.6353622535f,303.6958620199f,303.7563566215f, + 303.8168460601f,303.8773303378f,303.9378094564f,303.9982834178f,304.0587522239f,304.1192158767f, + 304.1796743780f,304.2401277297f,304.3005759338f,304.3610189920f,304.4214569065f,304.4818896789f, + 304.5423173113f,304.6027398054f,304.6631571633f,304.7235693868f,304.7839764778f,304.8443784381f, + 304.9047752697f,304.9651669745f,305.0255535543f,305.0859350110f,305.1463113465f,305.2066825627f, + 305.2670486615f,305.3274096447f,305.3877655143f,305.4481162720f,305.5084619199f,305.5688024596f, + 305.6291378932f,305.6894682225f,305.7497934494f,305.8101135757f,305.8704286033f,305.9307385341f, + 305.9910433699f,306.0513431126f,306.1116377640f,306.1719273261f,306.2322118007f,306.2924911897f, + 306.3527654948f,306.4130347180f,306.4732988611f,306.5335579260f,306.5938119145f,306.6540608286f, + 306.7143046699f,306.7745434404f,306.8347771420f,306.8950057764f,306.9552293456f,307.0154478513f, + 307.0756612955f,307.1358696799f,307.1960730064f,307.2562712768f,307.3164644931f,307.3766526569f, + 307.4368357702f,307.4970138348f,307.5571868526f,307.6173548253f,307.6775177547f,307.7376756429f, + 307.7978284914f,307.8579763023f,307.9181190772f,307.9782568181f,308.0383895268f,308.0985172050f, + 308.1586398547f,308.2187574776f,308.2788700755f,308.3389776504f,308.3990802039f,308.4591777379f, + 308.5192702542f,308.5793577547f,308.6394402412f,308.6995177154f,308.7595901791f,308.8196576343f, + 308.8797200827f,308.9397775260f,308.9998299662f,309.0598774050f,309.1199198442f,309.1799572857f, + 309.2399897311f,309.3000171824f,309.3600396414f,309.4200571097f,309.4800695893f,309.5400770819f, + 309.6000795893f,309.6600771133f,309.7200696557f,309.7800572184f,309.8400398030f,309.9000174114f, + 309.9599900453f,310.0199577066f,310.0799203970f,310.1398781184f,310.1998308724f,310.2597786610f, + 310.3197214858f,310.3796593487f,310.4395922514f,310.4995201957f,310.5594431834f,310.6193612162f, + 310.6792742960f,310.7391824245f,310.7990856035f,310.8589838348f,310.9188771200f,310.9787654611f, + 311.0386488597f,311.0985273177f,311.1584008367f,311.2182694186f,311.2781330651f,311.3379917781f, + 311.3978455591f,311.4576944101f,311.5175383327f,311.5773773288f,311.6372114000f,311.6970405482f, + 311.7568647751f,311.8166840824f,311.8764984719f,311.9363079454f,311.9961125046f,312.0559121512f, + 312.1157068870f,312.1754967138f,312.2352816333f,312.2950616472f,312.3548367573f,312.4146069653f, + 312.4743722729f,312.5341326820f,312.5938881942f,312.6536388113f,312.7133845350f,312.7731253671f, + 312.8328613093f,312.8925923633f,312.9523185308f,313.0120398136f,313.0717562135f,313.1314677321f, + 313.1911743712f,313.2508761325f,313.3105730177f,313.3702650286f,313.4299521669f,313.4896344342f, + 313.5493118324f,313.6089843632f,313.6686520282f,313.7283148292f,313.7879727679f,313.8476258460f, + 313.9072740652f,313.9669174274f,314.0265559340f,314.0861895870f,314.1458183879f,314.2054423385f, + 314.2650614406f,314.3246756957f,314.3842851057f,314.4438896722f,314.5034893970f,314.5630842817f, + 314.6226743280f,314.6822595376f,314.7418399123f,314.8014154538f,314.8609861637f,314.9205520437f, + 314.9801130956f,315.0396693210f,315.0992207216f,315.1587672991f,315.2183090552f,315.2778459917f, + 315.3373781101f,315.3969054122f,315.4564278997f,315.5159455743f,315.5754584375f,315.6349664912f, + 315.6944697371f,315.7539681767f,315.8134618118f,315.8729506440f,315.9324346751f,315.9919139067f, + 316.0513883404f,316.1108579781f,316.1703228213f,316.2297828717f,316.2892381310f,316.3486886009f, + 316.4081342830f,316.4675751790f,316.5270112905f,316.5864426194f,316.6458691671f,316.7052909354f, + 316.7647079260f,316.8241201404f,316.8835275805f,316.9429302477f,317.0023281439f,317.0617212706f, + 317.1211096295f,317.1804932223f,317.2398720506f,317.2992461161f,317.3586154205f,317.4179799653f, + 317.4773397523f,317.5366947831f,317.5960450593f,317.6553905826f,317.7147313547f,317.7740673771f, + 317.8333986516f,317.8927251798f,317.9520469634f,318.0113640039f,318.0706763030f,318.1299838624f, + 318.1892866837f,318.2485847685f,318.3078781185f,318.3671667353f,318.4264506206f,318.4857297760f, + 318.5450042031f,318.6042739035f,318.6635388790f,318.7227991311f,318.7820546614f,318.8413054716f, + 318.9005515634f,318.9597929382f,319.0190295979f,319.0782615439f,319.1374887780f,319.1967113017f, + 319.2559291167f,319.3151422245f,319.3743506269f,319.4335543254f,319.4927533216f,319.5519476172f, + 319.6111372138f,319.6703221130f,319.7295023164f,319.7886778257f,319.8478486424f,319.9070147681f, + 319.9661762045f,320.0253329532f,320.0844850157f,320.1436323938f,320.2027750889f,320.2619131028f, + 320.3210464369f,320.3801750930f,320.4392990726f,320.4984183773f,320.5575330088f,320.6166429685f, + 320.6757482582f,320.7348488794f,320.7939448337f,320.8530361228f,320.9121227481f,320.9712047114f, + 321.0302820141f,321.0893546579f,321.1484226445f,321.2074859752f,321.2665446519f,321.3255986760f, + 321.3846480491f,321.4436927729f,321.5027328489f,321.5617682787f,321.6207990638f,321.6798252060f, + 321.7388467066f,321.7978635674f,321.8568757899f,321.9158833757f,321.9748863264f,322.0338846435f, + 322.0928783286f,322.1518673833f,322.2108518092f,322.2698316078f,322.3288067807f,322.3877773296f, + 322.4467432559f,322.5057045612f,322.5646612471f,322.6236133151f,322.6825607669f,322.7415036040f, + 322.8004418280f,322.8593754404f,322.9183044427f,322.9772288367f,323.0361486237f,323.0950638054f, + 323.1539743833f,323.2128803591f,323.2717817341f,323.3306785101f,323.3895706886f,323.4484582710f, + 323.5073412591f,323.5662196542f,323.6250934580f,323.6839626721f,323.7428272979f,323.8016873371f, + 323.8605427911f,323.9193936615f,323.9782399499f,324.0370816579f,324.0959187868f,324.1547513384f, + 324.2135793141f,324.2724027156f,324.3312215442f,324.3900358016f,324.4488454893f,324.5076506089f, + 324.5664511618f,324.6252471496f,324.6840385739f,324.7428254362f,324.8016077380f,324.8603854809f, + 324.9191586663f,324.9779272959f,325.0366913711f,325.0954508935f,325.1542058646f,325.2129562859f, + 325.2717021589f,325.3304434853f,325.3891802664f,325.4479125039f,325.5066401993f,325.5653633540f, + 325.6240819696f,325.6827960476f,325.7415055896f,325.8002105970f,325.8589110714f,325.9176070142f, + 325.9762984271f,326.0349853115f,326.0936676689f,326.1523455009f,326.2110188089f,326.2696875944f, + 326.3283518591f,326.3870116043f,326.4456668317f,326.5043175426f,326.5629637386f,326.6216054213f, + 326.6802425921f,326.7388752525f,326.7975034040f,326.8561270481f,326.9147461864f,326.9733608203f, + 327.0319709513f,327.0905765810f,327.1491777108f,327.2077743422f,327.2663664767f,327.3249541158f, + 327.3835372611f,327.4421159139f,327.5006900758f,327.5592597483f,327.6178249329f,327.6763856311f, + 327.7349418443f,327.7934935740f,327.8520408218f,327.9105835891f,327.9691218774f,328.0276556882f, + 328.0861850229f,328.1447098831f,328.2032302702f,328.2617461858f,328.3202576312f,328.3787646080f, + 328.4372671177f,328.4957651617f,328.5542587415f,328.6127478585f,328.6712325144f,328.7297127105f, + 328.7881884482f,328.8466597292f,328.9051265548f,328.9635889265f,329.0220468458f,329.0805003142f, + 329.1389493331f,329.1973939040f,329.2558340283f,329.3142697076f,329.3727009433f,329.4311277368f, + 329.4895500897f,329.5479680033f,329.6063814791f,329.6647905187f,329.7231951234f,329.7815952948f, + 329.8399910342f,329.8983823432f,329.9567692231f,330.0151516755f,330.0735297018f,330.1319033035f, + 330.1902724820f,330.2486372387f,330.3069975751f,330.3653534927f,330.4237049929f,330.4820520772f, + 330.5403947470f,330.5987330037f,330.6570668488f,330.7153962838f,330.7737213101f,330.8320419291f, + 330.8903581422f,330.9486699510f,331.0069773569f,331.0652803612f,331.1235789655f,331.1818731712f, + 331.2401629797f,331.2984483924f,331.3567294109f,331.4150060364f,331.4732782705f,331.5315461146f, + 331.5898095702f,331.6480686385f,331.7063233212f,331.7645736196f,331.8228195351f,331.8810610693f, + 331.9392982234f,331.9975309989f,332.0557593973f,332.1139834200f,332.1722030684f,332.2304183439f, + 332.2886292480f,332.3468357821f,332.4050379475f,332.4632357458f, +}; +static float get_visual_mask(float value) { + SkASSERT(value >= 0.0f); + SkASSERT(value < 4000.0f); + return gVisualMaskTable[(int)value]; +} +} diff --git a/chromium/third_party/skia/tools/skpdiff/diff_viewer.js b/chromium/third_party/skia/tools/skpdiff/diff_viewer.js new file mode 100644 index 00000000000..6be434b4db1 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/diff_viewer.js @@ -0,0 +1,306 @@ +var MAX_SWAP_IMG_SIZE = 400; +var MAGNIFIER_WIDTH = 200; +var MAGNIFIER_HEIGHT = 200; +var MAGNIFIER_HALF_WIDTH = MAGNIFIER_WIDTH * 0.5; +var MAGNIFIER_HALF_HEIGHT = MAGNIFIER_HEIGHT * 0.5; +// TODO add support for a magnified scale factor +var MAGNIFIER_SCALE_FACTOR = 2.0; + +angular.module('diff_viewer', []). +directive('imgCompare', function() { + // Custom directive for comparing (3-way) images + return { + restrict: 'E', // The directive can be used as an element name + replace: true, // The directive replaces itself with the template + template: '<canvas/>', + scope: true, + link: function(scope, elm, attrs, ctrl) { + var image = new Image(); + var canvas = elm[0]; + var ctx = canvas.getContext('2d'); + + var magnifyContent = false; + var maskCanvas = false; + + // When the type attribute changes, load the image and then render + attrs.$observe('type', function(value) { + switch(value) { + case "alphaMask": + image.src = scope.record.differencePath; + maskCanvas = true; + break; + case "baseline": + image.src = scope.record.baselinePath; + magnifyContent = true; + break; + case "test": + image.src = scope.record.testPath; + magnifyContent = true; + break; + default: + console.log("Unknown type attribute on <img-compare>: " + value); + return; + } + + image.onload = function() { + // compute the scaled image width/height for image and canvas + var divisor = 1; + // Make it so the maximum size of an image is MAX_SWAP_IMG_SIZE, + // and the images are scaled down in halves. + while ((image.width / divisor) > MAX_SWAP_IMG_SIZE) { + divisor *= 2; + } + + scope.setImgScaleFactor(1 / divisor); + + // Set canvas to correct size + canvas.width = image.width * scope.imgScaleFactor; + canvas.height = image.height * scope.imgScaleFactor; + + // update the size for non-alphaMask canvas when loading baseline image + if (!scope.maskSizeUpdated) { + if (!maskCanvas) { + scope.updateMaskCanvasSize({width: canvas.width, height: canvas.height}); + } + scope.maskCanvasSizeUpdated(true); + } + + // render the image onto the canvas + scope.renderImage(); + } + }); + + // when updatedMaskSize changes, update mask canvas size. + scope.$watch('updatedMaskSize', function(updatedSize) { + if (!maskCanvas) { + return; + } + + canvas.width = updatedSize.width; + canvas.height = updatedSize.height; + }); + + // When the magnify attribute changes, render the magnified rect at + // the default zoom level. + scope.$watch('magnifyCenter', function(magCenter) { + if (!magnifyContent) { + return; + } + + scope.renderImage(); + + if (!magCenter) { + return; + } + + var magX = magCenter.x - MAGNIFIER_HALF_WIDTH; + var magY = magCenter.y - MAGNIFIER_HALF_HEIGHT; + + var magMaxX = canvas.width - MAGNIFIER_WIDTH; + var magMaxY = canvas.height - MAGNIFIER_HEIGHT; + + var magRect = { x: Math.max(0, Math.min(magX, magMaxX)), + y: Math.max(0, Math.min(magY, magMaxY)), + width: MAGNIFIER_WIDTH, + height: MAGNIFIER_HEIGHT + }; + + var imgRect = { x: (magCenter.x / scope.imgScaleFactor) - MAGNIFIER_HALF_WIDTH, + y: (magCenter.y / scope.imgScaleFactor) - MAGNIFIER_HALF_HEIGHT, + width: MAGNIFIER_WIDTH, + height: MAGNIFIER_HEIGHT + }; + + // draw the magnified image + ctx.clearRect(magRect.x, magRect.y, magRect.width, magRect.height); + ctx.drawImage(image, imgRect.x, imgRect.y, imgRect.width, imgRect.height, + magRect.x, magRect.y, magRect.width, magRect.height); + + // draw the outline rect + ctx.beginPath(); + ctx.rect(magRect.x, magRect.y, magRect.width, magRect.height); + ctx.lineWidth = 2; + ctx.strokeStyle = 'red'; + ctx.stroke(); + + }); + + // render the image to the canvas. This is often done every frame prior + // to any special effects (i.e. magnification). + scope.renderImage = function() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(image, 0, 0, canvas.width, canvas.height); + }; + + // compute a rect (x,y,width,height) that represents the bounding box for + // the magnification effect + scope.computeMagnifierOutline = function(event) { + var scaledWidth = MAGNIFIER_WIDTH * scope.imgScaleFactor; + var scaledHeight = MAGNIFIER_HEIGHT * scope.imgScaleFactor; + return { + x: event.offsetX - (scaledWidth * 0.5), + y: event.offsetY - (scaledHeight * 0.5), + width: scaledWidth, + height: scaledHeight + }; + }; + + // event handler for mouse events that triggers the magnification + // effect across the 3 images being compared. + scope.MagnifyDraw = function(event, startMagnify) { + if (startMagnify) { + scope.setMagnifierState(true); + } else if (!scope.magnifierOn) { + return; + } + + scope.renderImage(); + + // render the magnifier outline rect + var rect = scope.computeMagnifierOutline(event); + ctx.save(); + ctx.beginPath(); + ctx.rect(rect.x, rect.y, rect.width, rect.height); + ctx.lineWidth = 2; + ctx.strokeStyle = 'red'; + ctx.stroke(); + ctx.restore(); + + // update scope on baseline / test that will cause them to render + scope.setMagnifyCenter({x: event.offsetX, y: event.offsetY}); + }; + + // event handler that triggers the end of the magnification effect and + // resets all the canvases to their original state. + scope.MagnifyEnd = function(event) { + scope.renderImage(); + // update scope on baseline / test that will cause them to render + scope.setMagnifierState(false); + scope.setMagnifyCenter(undefined); + }; + } + }; +}); + +function ImageController($scope, $http, $location, $timeout, $parse) { + $scope.imgScaleFactor = 1.0; + $scope.magnifierOn = false; + $scope.magnifyCenter = undefined; + $scope.updatedMaskSize = undefined; + $scope.maskSizeUpdated = false; + + $scope.setImgScaleFactor = function(scaleFactor) { + $scope.imgScaleFactor = scaleFactor; + } + + $scope.setMagnifierState = function(magnifierOn) { + $scope.magnifierOn = magnifierOn; + } + + $scope.setMagnifyCenter = function(magnifyCenter) { + $scope.magnifyCenter = magnifyCenter; + } + + $scope.updateMaskCanvasSize = function(updatedSize) { + $scope.updatedMaskSize = updatedSize; + } + + $scope.maskCanvasSizeUpdated = function(flag) { + $scope.maskSizeUpdated = flag; + } +} + +function DiffListController($scope, $http, $location, $timeout, $parse) { + // Detect if we are running the web server version of the viewer. If so, we set a flag and + // enable some extra functionality of the website for rebaselining. + $scope.isDynamic = ($location.protocol() == "http" || $location.protocol() == "https"); + + // Label each kind of differ for the sort buttons. + $scope.differs = [ + { + "title": "Different Pixels" + }, + { + "title": "Perceptual Difference" + } + ]; + + // Puts the records within AngularJS scope + $scope.records = SkPDiffRecords.records; + + // Keep track of the index of the last record to change so that shift clicking knows what range + // of records to apply the action to. + $scope.lastSelectedIndex = undefined; + + // Indicates which diff metric is used for sorting + $scope.sortIndex = 1; + + // Called by the sort buttons to adjust the metric used for sorting + $scope.setSortIndex = function(idx) { + $scope.sortIndex = idx; + + // Because the index of things has most likely changed, the ranges of shift clicking no + // longer make sense from the user's point of view. We reset it to avoid confusion. + $scope.lastSelectedIndex = undefined; + }; + + // A predicate for pulling out the number used for sorting + $scope.sortingDiffer = function(record) { + return record.diffs[$scope.sortIndex].result; + }; + + // Flash status indicator on the page, and then remove it so the style can potentially be + // reapplied later. + $scope.flashStatus = function(success) { + var flashStyle = success ? "success-flash" : "failure-flash"; + var flashDurationMillis = success ? 500 : 800; + + // Store the style in the record. The row will pick up the style this way instead of through + // index because index can change with sort order. + $scope.statusClass = flashStyle; + + // The animation cannot be repeated unless the class is removed the element. + $timeout(function() { + $scope.statusClass = ""; + }, flashDurationMillis); + }; + + $scope.selectedRebaseline = function(index, event) { + // Retrieve the records in the same order they are displayed. + var recordsInOrder = $parse("records | orderBy:sortingDiffer")($scope); + + // If the user is shift clicking, apply the last tick/untick to all elements in between this + // record, and the last one they ticked/unticked. + if (event.shiftKey && $scope.lastSelectedIndex !== undefined) { + var currentAction = recordsInOrder[index].isRebaselined; + var smallerIndex = Math.min($scope.lastSelectedIndex, index); + var largerIndex = Math.max($scope.lastSelectedIndex, index); + for (var recordIndex = smallerIndex; recordIndex <= largerIndex; recordIndex++) { + recordsInOrder[recordIndex].isRebaselined = currentAction; + } + $scope.lastSelectedIndex = index; + } + else + { + $scope.lastSelectedIndex = index; + } + + }; + + $scope.commitRebaselines = function() { + // Gather up all records that have the rebaseline set. + var rebaselines = []; + for (var recordIndex = 0; recordIndex < $scope.records.length; recordIndex++) { + if ($scope.records[recordIndex].isRebaselined) { + rebaselines.push($scope.records[recordIndex].testPath); + } + } + $http.post("/commit_rebaselines", { + "rebaselines": rebaselines + }).success(function(data) { + $scope.flashStatus(data.success); + }).error(function() { + $scope.flashStatus(false); + }); + }; +} diff --git a/chromium/third_party/skia/tools/skpdiff/generate_pmetric_tables.py b/chromium/third_party/skia/tools/skpdiff/generate_pmetric_tables.py new file mode 100755 index 00000000000..71c9114c06d --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/generate_pmetric_tables.py @@ -0,0 +1,149 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from __future__ import print_function +from math import * + + +COPYRIGHT = '''/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */''' + + +HELP = '// To regenerate SkPMetricUtil_generated.h, simply run ./generate_pmetric_tables.py' + + +# From Barten SPIE 1989 +def contrast_sensitivity(cycles_per_degree, luminance): + a = 440.0 * pow(1.0 + 0.7 / luminance, -0.2) + b = 0.3 * pow(1 + 100.0 / luminance, 0.15) + return a * cycles_per_degree * exp(-b * cycles_per_degree) * sqrt(1.0 + 0.06 * exp(b * cycles_per_degree)) + + +# From Ward Larson Siggraph 1997 +def threshold_vs_intensity(adaptation_luminance): + log_lum = float('-inf') # Works in Python 2.6+ + try: + log_lum = log10(adaptation_luminance) + except ValueError: + pass + + x = 0.0 + + if log_lum < -3.94: + x = -2.86 + + elif log_lum < -1.44: + x = pow(0.405 * log_lum + 1.6, 2.18) - 2.86 + + elif log_lum < -0.0184: + x = log_lum - 0.395 + + elif log_lum < 1.9: + x = pow(0.249 * log_lum + 0.65, 2.7) - 0.72 + + else: + x = log_lum - 1.255 + + return pow(10.0, x) + + +# From Daly 1993 +def visual_mask(contrast): + x = pow(392.498 * contrast, 0.7) + x = pow(0.0153 * x, 4.0) + return pow(1.0 + x, 0.25) + + +# float gCubeRootTable[] +CUBE_ROOT_ACCESS_FUNCTION = ''' +static float get_cube_root(float value) { + SkASSERT(value >= 0.0f); + SkASSERT(value * 1023.0f < 1024.0f); + return gCubeRootTable[(int)(value * 1023.0f)]; +} +''' +def generate_cube_root_table(stream): + print('static float gCubeRootTable[] = {', end='', file=stream) + for i in range(1024): + if i % 6 == 0: + print('\n ', end='', file=stream) + print("%.10f" % pow(i / 1024.0, 1.0 / 3.0), end='f,', file=stream) + print('\n};', end='', file=stream) + print(CUBE_ROOT_ACCESS_FUNCTION, file=stream) + + +# float gGammaTable[] +GAMMA_ACCESS_FUNCTION = ''' +static float get_gamma(unsigned char value) { + return gGammaTable[value]; +} +''' +def generate_gamma_table(stream): + print('static float gGammaTable[] = {', end='', file=stream) + for i in range(256): + if i % 6 == 0: + print('\n ', end='', file=stream) + print("%.10f" % pow(i / 255.0, 2.2), end='f,', file=stream) + print('\n};', end='', file=stream) + print(GAMMA_ACCESS_FUNCTION, file=stream) + + +# float gTVITable[] +TVI_ACCESS_FUNCTION = ''' +static float get_threshold_vs_intensity(float value) { + SkASSERT(value >= 0.0f); + SkASSERT(value < 100.0f); + return gTVITable[(int)(value * 100.0f)]; +} +''' +def generate_tvi_table(stream): + print('static float gTVITable[] = {', end='', file=stream) + for i in range(10000): + if i % 6 == 0: + print('\n ', end='', file=stream) + print("%.10f" % threshold_vs_intensity(i / 100.0), end='f,', file=stream) + print('\n};', end='', file=stream) + print(TVI_ACCESS_FUNCTION, file=stream) + + +# float gVisualMaskTable[] +VISUAL_MASK_DOMAIN = 4000 +VISUAL_MASK_ACCESS_FUNCTION = ''' +static float get_visual_mask(float value) {{ + SkASSERT(value >= 0.0f); + SkASSERT(value < {}.0f); + return gVisualMaskTable[(int)value]; +}}''' +def generate_visual_mask_table(stream): + print('static float gVisualMaskTable[] = {', end='', file=stream) + for i in range(VISUAL_MASK_DOMAIN): + if i % 6 == 0: + print('\n ', end='', file=stream) + print("%.10f" % visual_mask(i), end='f,', file=stream) + print('\n};', end='', file=stream) + print(VISUAL_MASK_ACCESS_FUNCTION.format(VISUAL_MASK_DOMAIN), file=stream) + + +def generate_lookup_tables(stream): + print(COPYRIGHT, file=stream) + print(HELP, file=stream) + print('namespace SkPMetricUtil {', file=stream) + generate_cube_root_table(stream) + generate_gamma_table(stream) + generate_tvi_table(stream) + generate_visual_mask_table(stream) + print('}', file=stream) + + +def main(): + pmetric_util_out = open('SkPMetricUtil_generated.h', 'wb') + generate_lookup_tables(pmetric_util_out) + pmetric_util_out.close() + + +if __name__ == '__main__': + main() diff --git a/chromium/third_party/skia/tools/skpdiff/skpdiff_main.cpp b/chromium/third_party/skia/tools/skpdiff/skpdiff_main.cpp new file mode 100644 index 00000000000..b1bf9173c7c --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/skpdiff_main.cpp @@ -0,0 +1,236 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#if SK_SUPPORT_OPENCL + +#define __NO_STD_VECTOR // Uses cl::vectpr instead of std::vectpr +#define __NO_STD_STRING // Uses cl::STRING_CLASS instead of std::string +#if SK_BUILD_FOR_MAC +// Note that some macs don't have this header and it can be downloaded from the Khronos registry +# include <OpenCL/cl.hpp> +#else +# include <CL/cl.hpp> +#endif + +#endif + +#include "SkCommandLineFlags.h" +#include "SkGraphics.h" +#include "SkStream.h" +#include "SkTDArray.h" + +#include "SkDifferentPixelsMetric.h" +#include "SkDiffContext.h" +#include "SkImageDiffer.h" +#include "SkPMetric.h" +#include "skpdiff_util.h" + +#include "SkForceLinking.h" +__SK_FORCE_IMAGE_DECODER_LINKING; + +// Command line argument definitions go here +DEFINE_bool2(list, l, false, "List out available differs"); +DEFINE_string2(differs, d, "", "The names of the differs to use or all of them by default"); +DEFINE_string2(folders, f, "", "Compare two folders with identical subfile names: <baseline folder> <test folder>"); +DEFINE_string2(patterns, p, "", "Use two patterns to compare images: <baseline> <test>"); +DEFINE_string2(output, o, "", "Writes the output of these diffs to output: <output>"); +DEFINE_string(alphaDir, "", "Writes the alpha mask of these diffs to output: <output>"); +DEFINE_bool(jsonp, true, "Output JSON with padding"); +DEFINE_string(csv, "", "Writes the output of these diffs to a csv file"); +DEFINE_int32(threads, -1, "run N threads in parallel [default is derived from CPUs available]"); + +#if SK_SUPPORT_OPENCL +/// A callback for any OpenCL errors +static void CL_CALLBACK error_notify(const char* errorInfo, const void* privateInfoSize, ::size_t cb, void* userData) { + SkDebugf("OpenCL error notify: %s\n", errorInfo); + exit(1); +} + +/// Creates a device and context with OpenCL +static bool init_device_and_context(cl::Device* device, cl::Context* context) { + // Query for a platform + cl::vector<cl::Platform> platformList; + cl::Platform::get(&platformList); + SkDebugf("The number of platforms is %u\n", platformList.size()); + + // Print some information about the platform for debugging + cl::Platform& platform = platformList[0]; + cl::STRING_CLASS platformName; + platform.getInfo(CL_PLATFORM_NAME, &platformName); + SkDebugf("Platform index 0 is named %s\n", platformName.c_str()); + + // Query for a device + cl::vector<cl::Device> deviceList; + platform.getDevices(CL_DEVICE_TYPE_ALL, &deviceList); + SkDebugf("The number of devices is %u\n", deviceList.size()); + + // Print some information about the device for debugging + *device = deviceList[0]; + cl::STRING_CLASS deviceName; + device->getInfo(CL_DEVICE_NAME, &deviceName); + SkDebugf("Device index 0 is named %s\n", deviceName.c_str()); + + // Create a CL context and check for all errors + cl_int contextErr = CL_SUCCESS; + *context = cl::Context(deviceList, NULL, error_notify, NULL, &contextErr); + if (contextErr != CL_SUCCESS) { + SkDebugf("Context creation failed: %s\n", cl_error_to_string(contextErr)); + return false; + } + + return true; +} + +static bool init_cl_diff(SkImageDiffer* differ) { + // Setup OpenCL + cl::Device device; + cl::Context context; + if (!init_device_and_context(&device, &context)) { + return false; + } + + // Setup our differ of choice + SkCLImageDiffer* clDiffer = (SkCLImageDiffer*)differ; + return clDiffer->init(device(), context()); +} +#endif + +// TODO Find a better home for the diff registry. One possibility is to have the differs self +// register. + +// List here every differ +SkDifferentPixelsMetric gDiffPixel; +SkPMetric gPDiff; + +// A null terminated array of pointer to every differ declared above +SkImageDiffer* gDiffers[] = { &gDiffPixel, &gPDiff, NULL }; + +int tool_main(int argc, char * argv[]); +int tool_main(int argc, char * argv[]) { + // Setup command line parsing + SkCommandLineFlags::SetUsage("Compare images using various metrics."); + SkCommandLineFlags::Parse(argc, argv); + + // Needed by various Skia components + SkAutoGraphics ag; + + if (FLAGS_list) { + SkDebugf("Available Metrics:\n"); + } + + // Figure which differs the user chose, and optionally print them if the user requests it + SkTDArray<SkImageDiffer*> chosenDiffers; + for (int differIndex = 0; NULL != gDiffers[differIndex]; differIndex++) { + SkImageDiffer* differ = gDiffers[differIndex]; + if (FLAGS_list) { + SkDebugf(" %s", differ->getName()); + SkDebugf("\n"); + } + + // Check if this differ was chosen by any of the flags. Initialize them if they were chosen. + if (FLAGS_differs.isEmpty()) { + // If no differs were chosen, they all get added + if (differ->requiresOpenCL()) { +#if SK_SUPPORT_OPENCL + init_cl_diff(differ); + chosenDiffers.push(differ); +#endif + } else { + chosenDiffers.push(differ); + } + } else { + for (int flagIndex = 0; flagIndex < FLAGS_differs.count(); flagIndex++) { + if (SkString(FLAGS_differs[flagIndex]).equals(differ->getName())) { + // Initialize OpenCL for the differ if it needs it and support was compiled in. + if (differ->requiresOpenCL()) { +#if SK_SUPPORT_OPENCL + init_cl_diff(differ); + chosenDiffers.push(differ); +#endif + } else { + chosenDiffers.push(differ); + } + break; + } + } + } + } + + // Don't attempt to initialize the differ if we aren't going to use it + if (FLAGS_folders.isEmpty() && FLAGS_patterns.isEmpty()) { + return 0; + } + + // Validate command line flags + if (!FLAGS_folders.isEmpty()) { + if (2 != FLAGS_folders.count()) { + SkDebugf("Folders flag expects two arguments: <baseline folder> <test folder>\n"); + return 1; + } + } + + if (!FLAGS_patterns.isEmpty()) { + if (2 != FLAGS_patterns.count()) { + SkDebugf("Patterns flag expects two arguments: <baseline pattern> <test pattern>\n"); + return 1; + } + } + + if (!FLAGS_csv.isEmpty()) { + if (1 != FLAGS_csv.count()) { + SkDebugf("csv flag expects one argument: <csv file>\n"); + return 1; + } + } + + if (!FLAGS_alphaDir.isEmpty()) { + if (1 != FLAGS_alphaDir.count()) { + SkDebugf("alphaDir flag expects one argument: <directory>\n"); + return 1; + } + } + + SkDiffContext ctx; + ctx.setDiffers(chosenDiffers); + + if (!FLAGS_alphaDir.isEmpty()) { + ctx.setDifferenceDir(SkString(FLAGS_alphaDir[0])); + } + + if (FLAGS_threads >= 0) { + ctx.setThreadCount(FLAGS_threads); + } + + // Perform a folder diff if one is requested + if (!FLAGS_folders.isEmpty()) { + ctx.diffDirectories(FLAGS_folders[0], FLAGS_folders[1]); + } + + // Perform a pattern diff if one is requested + if (!FLAGS_patterns.isEmpty()) { + ctx.diffPatterns(FLAGS_patterns[0], FLAGS_patterns[1]); + } + + // Output to the file specified + if (!FLAGS_output.isEmpty()) { + SkFILEWStream outputStream(FLAGS_output[0]); + ctx.outputRecords(outputStream, FLAGS_jsonp); + } + + if (!FLAGS_csv.isEmpty()) { + SkFILEWStream outputStream(FLAGS_csv[0]); + ctx.outputCsv(outputStream); + } + + return 0; +} + +#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL) +int main(int argc, char * argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/skpdiff/skpdiff_server.py b/chromium/third_party/skia/tools/skpdiff/skpdiff_server.py new file mode 100755 index 00000000000..15ff8a9dab5 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/skpdiff_server.py @@ -0,0 +1,580 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from __future__ import print_function +import argparse +import BaseHTTPServer +import json +import os +import os.path +import re +import subprocess +import sys +import tempfile +import urllib2 + +# Grab the script path because that is where all the static assets are +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) + +# Find the tools directory for python imports +TOOLS_DIR = os.path.dirname(SCRIPT_DIR) + +# Find the root of the skia trunk for finding skpdiff binary +SKIA_ROOT_DIR = os.path.dirname(TOOLS_DIR) + +# Find the default location of gm expectations +DEFAULT_GM_EXPECTATIONS_DIR = os.path.join(SKIA_ROOT_DIR, 'expectations', 'gm') + +# Imports from within Skia +if TOOLS_DIR not in sys.path: + sys.path.append(TOOLS_DIR) +GM_DIR = os.path.join(SKIA_ROOT_DIR, 'gm') +if GM_DIR not in sys.path: + sys.path.append(GM_DIR) +import gm_json +import jsondiff + +# A simple dictionary of file name extensions to MIME types. The empty string +# entry is used as the default when no extension was given or if the extension +# has no entry in this dictionary. +MIME_TYPE_MAP = {'': 'application/octet-stream', + 'html': 'text/html', + 'css': 'text/css', + 'png': 'image/png', + 'js': 'application/javascript', + 'json': 'application/json' + } + + +IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) + +SKPDIFF_INVOKE_FORMAT = '{} --jsonp=false -o {} -f {} {}' + + +def get_skpdiff_path(user_path=None): + """Find the skpdiff binary. + + @param user_path If none, searches in Release and Debug out directories of + the skia root. If set, checks that the path is a real file and + returns it. + """ + skpdiff_path = None + possible_paths = [] + + # Use the user given path, or try out some good default paths. + if user_path: + possible_paths.append(user_path) + else: + possible_paths.append(os.path.join(SKIA_ROOT_DIR, 'out', + 'Release', 'skpdiff')) + possible_paths.append(os.path.join(SKIA_ROOT_DIR, 'out', + 'Release', 'skpdiff.exe')) + possible_paths.append(os.path.join(SKIA_ROOT_DIR, 'out', + 'Debug', 'skpdiff')) + possible_paths.append(os.path.join(SKIA_ROOT_DIR, 'out', + 'Debug', 'skpdiff.exe')) + # Use the first path that actually points to the binary + for possible_path in possible_paths: + if os.path.isfile(possible_path): + skpdiff_path = possible_path + break + + # If skpdiff was not found, print out diagnostic info for the user. + if skpdiff_path is None: + print('Could not find skpdiff binary. Either build it into the ' + + 'default directory, or specify the path on the command line.') + print('skpdiff paths tried:') + for possible_path in possible_paths: + print(' ', possible_path) + return skpdiff_path + + +def download_file(url, output_path): + """Download the file at url and place it in output_path""" + reader = urllib2.urlopen(url) + with open(output_path, 'wb') as writer: + writer.write(reader.read()) + + +def download_gm_image(image_name, image_path, hash_val): + """Download the gm result into the given path. + + @param image_name The GM file name, for example imageblur_gpu.png. + @param image_path Path to place the image. + @param hash_val The hash value of the image. + """ + if hash_val is None: + return + + # Separate the test name from a image name + image_match = IMAGE_FILENAME_RE.match(image_name) + test_name = image_match.group(1) + + # Calculate the URL of the requested image + image_url = gm_json.CreateGmActualUrl( + test_name, gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5, hash_val) + + # Download the image as requested + download_file(image_url, image_path) + + +def get_image_set_from_skpdiff(skpdiff_records): + """Get the set of all images references in the given records. + + @param skpdiff_records An array of records, which are dictionary objects. + """ + expected_set = frozenset([r['baselinePath'] for r in skpdiff_records]) + actual_set = frozenset([r['testPath'] for r in skpdiff_records]) + return expected_set | actual_set + + +def set_expected_hash_in_json(expected_results_json, image_name, hash_value): + """Set the expected hash for the object extracted from + expected-results.json. Note that this only work with bitmap-64bitMD5 hash + types. + + @param expected_results_json The Python dictionary with the results to + modify. + @param image_name The name of the image to set the hash of. + @param hash_value The hash to set for the image. + """ + expected_results = expected_results_json[gm_json.JSONKEY_EXPECTEDRESULTS] + + if image_name in expected_results: + expected_results[image_name][gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS][0][1] = hash_value + else: + expected_results[image_name] = { + gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS: + [ + [ + gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5, + hash_value + ] + ] + } + + +def get_head_version(path): + """Get the version of the file at the given path stored inside the HEAD of + the git repository. It is returned as a string. + + @param path The path of the file whose HEAD is returned. It is assumed the + path is inside a git repo rooted at SKIA_ROOT_DIR. + """ + + # git-show will not work with absolute paths. This ensures we give it a path + # relative to the skia root. This path also has to use forward slashes, even + # on windows. + git_path = os.path.relpath(path, SKIA_ROOT_DIR).replace('\\', '/') + git_show_proc = subprocess.Popen(['git', 'show', 'HEAD:' + git_path], + stdout=subprocess.PIPE) + + # When invoked outside a shell, git will output the last committed version + # of the file directly to stdout. + git_version_content, _ = git_show_proc.communicate() + return git_version_content + + +class GMInstance: + """Information about a GM test result on a specific device: + - device_name = the name of the device that rendered it + - image_name = the GM test name and config + - expected_hash = the current expected hash value + - actual_hash = the actual hash value + - is_rebaselined = True if actual_hash is what is currently in the expected + results file, False otherwise. + """ + def __init__(self, + device_name, image_name, + expected_hash, actual_hash, + is_rebaselined): + self.device_name = device_name + self.image_name = image_name + self.expected_hash = expected_hash + self.actual_hash = actual_hash + self.is_rebaselined = is_rebaselined + + +class ExpectationsManager: + def __init__(self, expectations_dir, expected_name, updated_name, + skpdiff_path): + """ + @param expectations_dir The directory to traverse for results files. + This should resemble expectations/gm in the Skia trunk. + @param expected_name The name of the expected result files. These + are in the format of expected-results.json. + @param updated_name The name of the updated expected result files. + Normally this matches --expectations-filename-output for the + rebaseline.py tool. + @param skpdiff_path The path used to execute the skpdiff command. + """ + self._expectations_dir = expectations_dir + self._expected_name = expected_name + self._updated_name = updated_name + self._skpdiff_path = skpdiff_path + self._generate_gm_comparison() + + def _generate_gm_comparison(self): + """Generate all the data needed to compare GMs: + - determine which GMs changed + - download the changed images + - compare them with skpdiff + """ + + # Get the expectations and compare them with actual hashes + self._get_expectations() + + + # Create a temporary file tree that makes sense for skpdiff to operate + # on. We take the realpath of the new temp directory because some OSs + # (*cough* osx) put the temp directory behind a symlink that gets + # resolved later down the pipeline and breaks the image map. + image_output_dir = os.path.realpath(tempfile.mkdtemp('skpdiff')) + expected_image_dir = os.path.join(image_output_dir, 'expected') + actual_image_dir = os.path.join(image_output_dir, 'actual') + os.mkdir(expected_image_dir) + os.mkdir(actual_image_dir) + + # Download expected and actual images that differed into the temporary + # file tree. + self._download_expectation_images(expected_image_dir, actual_image_dir) + + # Invoke skpdiff with our downloaded images and place its results in the + # temporary directory. + self._skpdiff_output_path = os.path.join(image_output_dir, + 'skpdiff_output.json') + skpdiff_cmd = SKPDIFF_INVOKE_FORMAT.format(self._skpdiff_path, + self._skpdiff_output_path, + expected_image_dir, + actual_image_dir) + os.system(skpdiff_cmd) + self._load_skpdiff_output() + + + def _get_expectations(self): + """Fills self._expectations with GMInstance objects for each test whose + expectation is different between the following two files: + - the local filesystem's updated results file + - git's head version of the expected results file + """ + differ = jsondiff.GMDiffer() + self._expectations = [] + for root, dirs, files in os.walk(self._expectations_dir): + for expectation_file in files: + # There are many files in the expectations directory. We only + # care about expected results. + if expectation_file != self._expected_name: + continue + + # Get the name of the results file, and be sure there is an + # updated result to compare against. If there is not, there is + # no point in diffing this device. + expected_file_path = os.path.join(root, self._expected_name) + updated_file_path = os.path.join(root, self._updated_name) + if not os.path.isfile(updated_file_path): + continue + + # Always get the expected results from git because we may have + # changed them in a previous instance of the server. + expected_contents = get_head_version(expected_file_path) + updated_contents = None + with open(updated_file_path, 'rb') as updated_file: + updated_contents = updated_file.read() + + # Read the expected results on disk to determine what we've + # already rebaselined. + commited_contents = None + with open(expected_file_path, 'rb') as expected_file: + commited_contents = expected_file.read() + + # Find all expectations that did not match. + expected_diff = differ.GenerateDiffDictFromStrings( + expected_contents, + updated_contents) + + # Generate a set of images that have already been rebaselined + # onto disk. + rebaselined_diff = differ.GenerateDiffDictFromStrings( + expected_contents, + commited_contents) + + rebaselined_set = set(rebaselined_diff.keys()) + + # The name of the device corresponds to the name of the folder + # we are in. + device_name = os.path.basename(root) + + # Store old and new versions of the expectation for each GM + for image_name, hashes in expected_diff.iteritems(): + self._expectations.append( + GMInstance(device_name, image_name, + hashes['old'], hashes['new'], + image_name in rebaselined_set)) + + def _load_skpdiff_output(self): + """Loads the results of skpdiff and annotates them with whether they + have already been rebaselined or not. The resulting data is store in + self.skpdiff_records.""" + self.skpdiff_records = None + with open(self._skpdiff_output_path, 'rb') as skpdiff_output_file: + self.skpdiff_records = json.load(skpdiff_output_file)['records'] + for record in self.skpdiff_records: + record['isRebaselined'] = self.image_map[record['baselinePath']][1].is_rebaselined + + + def _download_expectation_images(self, expected_image_dir, actual_image_dir): + """Download the expected and actual images for the _expectations array. + + @param expected_image_dir The directory to download expected images + into. + @param actual_image_dir The directory to download actual images into. + """ + image_map = {} + + # Look through expectations and download their images. + for expectation in self._expectations: + # Build appropriate paths to download the images into. + expected_image_path = os.path.join(expected_image_dir, + expectation.device_name + '-' + + expectation.image_name) + + actual_image_path = os.path.join(actual_image_dir, + expectation.device_name + '-' + + expectation.image_name) + + print('Downloading %s for device %s' % ( + expectation.image_name, expectation.device_name)) + + # Download images + download_gm_image(expectation.image_name, + expected_image_path, + expectation.expected_hash) + + download_gm_image(expectation.image_name, + actual_image_path, + expectation.actual_hash) + + # Annotate the expectations with where the images were downloaded + # to. + expectation.expected_image_path = expected_image_path + expectation.actual_image_path = actual_image_path + + # Map the image paths back to the expectations. + image_map[expected_image_path] = (False, expectation) + image_map[actual_image_path] = (True, expectation) + + self.image_map = image_map + + def _set_expected_hash(self, device_name, image_name, hash_value): + """Set the expected hash for the image of the given device. This always + writes directly to the expected results file of the given device + + @param device_name The name of the device to write the hash to. + @param image_name The name of the image whose hash to set. + @param hash_value The value of the hash to set. + """ + + # Retrieve the expected results file as it is in the working tree + json_path = os.path.join(self._expectations_dir, device_name, + self._expected_name) + expectations = gm_json.LoadFromFile(json_path) + + # Set the specified hash. + set_expected_hash_in_json(expectations, image_name, hash_value) + + # Write it out to disk using gm_json to keep the formatting consistent. + gm_json.WriteToFile(expectations, json_path) + + def commit_rebaselines(self, rebaselines): + """Sets the expected results file to use the hashes of the images in + the rebaselines list. If a expected result image is not in rebaselines + at all, the old hash will be used. + + @param rebaselines A list of image paths to use the hash of. + """ + # Reset all expectations to their old hashes because some of them may + # have been set to the new hash by a previous call to this function. + for expectation in self._expectations: + expectation.is_rebaselined = False + self._set_expected_hash(expectation.device_name, + expectation.image_name, + expectation.expected_hash) + + # Take all the images to rebaseline + for image_path in rebaselines: + # Get the metadata about the image at the path. + is_actual, expectation = self.image_map[image_path] + + expectation.is_rebaselined = is_actual + expectation_hash = expectation.actual_hash if is_actual else\ + expectation.expected_hash + + # Write out that image's hash directly to the expected results file. + self._set_expected_hash(expectation.device_name, + expectation.image_name, + expectation_hash) + + self._load_skpdiff_output() + + +class SkPDiffHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def send_file(self, file_path): + # Grab the extension if there is one + extension = os.path.splitext(file_path)[1] + if len(extension) >= 1: + extension = extension[1:] + + # Determine the MIME type of the file from its extension + mime_type = MIME_TYPE_MAP.get(extension, MIME_TYPE_MAP['']) + + # Open the file and send it over HTTP + if os.path.isfile(file_path): + with open(file_path, 'rb') as sending_file: + self.send_response(200) + self.send_header('Content-type', mime_type) + self.end_headers() + self.wfile.write(sending_file.read()) + else: + self.send_error(404) + + def serve_if_in_dir(self, dir_path, file_path): + # Determine if the file exists relative to the given dir_path AND exists + # under the dir_path. This is to prevent accidentally serving files + # outside the directory intended using symlinks, or '../'. + real_path = os.path.normpath(os.path.join(dir_path, file_path)) + if os.path.commonprefix([real_path, dir_path]) == dir_path: + if os.path.isfile(real_path): + self.send_file(real_path) + return True + return False + + def do_GET(self): + # Simple rewrite rule of the root path to 'viewer.html' + if self.path == '' or self.path == '/': + self.path = '/viewer.html' + + # The [1:] chops off the leading '/' + file_path = self.path[1:] + + # Handle skpdiff_output.json manually because it is was processed by the + # server when it was started and does not exist as a file. + if file_path == 'skpdiff_output.json': + self.send_response(200) + self.send_header('Content-type', MIME_TYPE_MAP['json']) + self.end_headers() + + # Add JSONP padding to the JSON because the web page expects it. It + # expects it because it was designed to run with or without a web + # server. Without a web server, the only way to load JSON is with + # JSONP. + skpdiff_records = self.server.expectations_manager.skpdiff_records + self.wfile.write('var SkPDiffRecords = ') + json.dump({'records': skpdiff_records}, self.wfile) + self.wfile.write(';') + return + + # Attempt to send static asset files first. + if self.serve_if_in_dir(SCRIPT_DIR, file_path): + return + + # WARNING: Serving any file the user wants is incredibly insecure. Its + # redeeming quality is that we only serve gm files on a white list. + if self.path in self.server.image_set: + self.send_file(self.path) + return + + # If no file to send was found, just give the standard 404 + self.send_error(404) + + def do_POST(self): + if self.path == '/commit_rebaselines': + content_length = int(self.headers['Content-length']) + request_data = json.loads(self.rfile.read(content_length)) + rebaselines = request_data['rebaselines'] + self.server.expectations_manager.commit_rebaselines(rebaselines) + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write('{"success":true}') + return + + # If the we have no handler for this path, give em' the 404 + self.send_error(404) + + +def run_server(expectations_manager, port=8080): + # It's important to parse the results file so that we can make a set of + # images that the web page might request. + skpdiff_records = expectations_manager.skpdiff_records + image_set = get_image_set_from_skpdiff(skpdiff_records) + + # Do not bind to interfaces other than localhost because the server will + # attempt to serve files relative to the root directory as a last resort + # before 404ing. This means all of your files can be accessed from this + # server, so DO NOT let this server listen to anything but localhost. + server_address = ('127.0.0.1', port) + http_server = BaseHTTPServer.HTTPServer(server_address, SkPDiffHandler) + http_server.image_set = image_set + http_server.expectations_manager = expectations_manager + print('Navigate thine browser to: http://{}:{}/'.format(*server_address)) + http_server.serve_forever() + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--port', '-p', metavar='PORT', + type=int, + default=8080, + help='port to bind the server to; ' + + 'defaults to %(default)s', + ) + + parser.add_argument('--expectations-dir', metavar='EXPECTATIONS_DIR', + default=DEFAULT_GM_EXPECTATIONS_DIR, + help='path to the gm expectations; ' + + 'defaults to %(default)s' + ) + + parser.add_argument('--expected', + metavar='EXPECTATIONS_FILE_NAME', + default='expected-results.json', + help='the file name of the expectations JSON; ' + + 'defaults to %(default)s' + ) + + parser.add_argument('--updated', + metavar='UPDATED_FILE_NAME', + default='updated-results.json', + help='the file name of the updated expectations JSON;' + + ' defaults to %(default)s' + ) + + parser.add_argument('--skpdiff-path', metavar='SKPDIFF_PATH', + default=None, + help='the path to the skpdiff binary to use; ' + + 'defaults to out/Release/skpdiff or out/Default/skpdiff' + ) + + args = vars(parser.parse_args()) # Convert args into a python dict + + # Make sure we have access to an skpdiff binary + skpdiff_path = get_skpdiff_path(args['skpdiff_path']) + if skpdiff_path is None: + sys.exit(1) + + # Print out the paths of things for easier debugging + print('script dir :', SCRIPT_DIR) + print('tools dir :', TOOLS_DIR) + print('root dir :', SKIA_ROOT_DIR) + print('expectations dir :', args['expectations_dir']) + print('skpdiff path :', skpdiff_path) + + expectations_manager = ExpectationsManager(args['expectations_dir'], + args['expected'], + args['updated'], + skpdiff_path) + + run_server(expectations_manager, port=args['port']) + +if __name__ == '__main__': + main() diff --git a/chromium/third_party/skia/tools/skpdiff/skpdiff_util.cpp b/chromium/third_party/skia/tools/skpdiff/skpdiff_util.cpp new file mode 100644 index 00000000000..171721c3be9 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/skpdiff_util.cpp @@ -0,0 +1,206 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#if SK_BUILD_FOR_MAC || SK_BUILD_FOR_UNIX || SK_BUILD_FOR_ANDROID +# include <unistd.h> +# include <sys/time.h> +# include <dirent.h> +#endif + +#if SK_BUILD_FOR_MAC || SK_BUILD_FOR_UNIX +# include <glob.h> +#endif + +#if SK_BUILD_FOR_MAC +# include <sys/syslimits.h> // PATH_MAX is here for Macs +#endif + +#if SK_BUILD_FOR_WIN32 +# include <windows.h> +#endif + +#include <stdlib.h> +#include <time.h> +#include "SkOSFile.h" +#include "skpdiff_util.h" + +#if SK_SUPPORT_OPENCL +const char* cl_error_to_string(cl_int err) { + switch (err) { + case CL_SUCCESS: return "CL_SUCCESS"; + case CL_DEVICE_NOT_FOUND: return "CL_DEVICE_NOT_FOUND"; + case CL_DEVICE_NOT_AVAILABLE: return "CL_DEVICE_NOT_AVAILABLE"; + case CL_COMPILER_NOT_AVAILABLE: return "CL_COMPILER_NOT_AVAILABLE"; + case CL_MEM_OBJECT_ALLOCATION_FAILURE: return "CL_MEM_OBJECT_ALLOCATION_FAILURE"; + case CL_OUT_OF_RESOURCES: return "CL_OUT_OF_RESOURCES"; + case CL_OUT_OF_HOST_MEMORY: return "CL_OUT_OF_HOST_MEMORY"; + case CL_PROFILING_INFO_NOT_AVAILABLE: return "CL_PROFILING_INFO_NOT_AVAILABLE"; + case CL_MEM_COPY_OVERLAP: return "CL_MEM_COPY_OVERLAP"; + case CL_IMAGE_FORMAT_MISMATCH: return "CL_IMAGE_FORMAT_MISMATCH"; + case CL_IMAGE_FORMAT_NOT_SUPPORTED: return "CL_IMAGE_FORMAT_NOT_SUPPORTED"; + case CL_BUILD_PROGRAM_FAILURE: return "CL_BUILD_PROGRAM_FAILURE"; + case CL_MAP_FAILURE: return "CL_MAP_FAILURE"; + case CL_INVALID_VALUE: return "CL_INVALID_VALUE"; + case CL_INVALID_DEVICE_TYPE: return "CL_INVALID_DEVICE_TYPE"; + case CL_INVALID_PLATFORM: return "CL_INVALID_PLATFORM"; + case CL_INVALID_DEVICE: return "CL_INVALID_DEVICE"; + case CL_INVALID_CONTEXT: return "CL_INVALID_CONTEXT"; + case CL_INVALID_QUEUE_PROPERTIES: return "CL_INVALID_QUEUE_PROPERTIES"; + case CL_INVALID_COMMAND_QUEUE: return "CL_INVALID_COMMAND_QUEUE"; + case CL_INVALID_HOST_PTR: return "CL_INVALID_HOST_PTR"; + case CL_INVALID_MEM_OBJECT: return "CL_INVALID_MEM_OBJECT"; + case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR: return "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR"; + case CL_INVALID_IMAGE_SIZE: return "CL_INVALID_IMAGE_SIZE"; + case CL_INVALID_SAMPLER: return "CL_INVALID_SAMPLER"; + case CL_INVALID_BINARY: return "CL_INVALID_BINARY"; + case CL_INVALID_BUILD_OPTIONS: return "CL_INVALID_BUILD_OPTIONS"; + case CL_INVALID_PROGRAM: return "CL_INVALID_PROGRAM"; + case CL_INVALID_PROGRAM_EXECUTABLE: return "CL_INVALID_PROGRAM_EXECUTABLE"; + case CL_INVALID_KERNEL_NAME: return "CL_INVALID_KERNEL_NAME"; + case CL_INVALID_KERNEL_DEFINITION: return "CL_INVALID_KERNEL_DEFINITION"; + case CL_INVALID_KERNEL: return "CL_INVALID_KERNEL"; + case CL_INVALID_ARG_INDEX: return "CL_INVALID_ARG_INDEX"; + case CL_INVALID_ARG_VALUE: return "CL_INVALID_ARG_VALUE"; + case CL_INVALID_ARG_SIZE: return "CL_INVALID_ARG_SIZE"; + case CL_INVALID_KERNEL_ARGS: return "CL_INVALID_KERNEL_ARGS"; + case CL_INVALID_WORK_DIMENSION: return "CL_INVALID_WORK_DIMENSION"; + case CL_INVALID_WORK_GROUP_SIZE: return "CL_INVALID_WORK_GROUP_SIZE"; + case CL_INVALID_WORK_ITEM_SIZE: return "CL_INVALID_WORK_ITEM_SIZE"; + case CL_INVALID_GLOBAL_OFFSET: return "CL_INVALID_GLOBAL_OFFSET"; + case CL_INVALID_EVENT_WAIT_LIST: return "CL_INVALID_EVENT_WAIT_LIST"; + case CL_INVALID_EVENT: return "CL_INVALID_EVENT"; + case CL_INVALID_OPERATION: return "CL_INVALID_OPERATION"; + case CL_INVALID_GL_OBJECT: return "CL_INVALID_GL_OBJECT"; + case CL_INVALID_BUFFER_SIZE: return "CL_INVALID_BUFFER_SIZE"; + case CL_INVALID_MIP_LEVEL: return "CL_INVALID_MIP_LEVEL"; + default: return "UNKNOWN"; + } + return "UNKNOWN"; +} +#endif + +// TODO refactor BenchTimer to be used here +double get_seconds() { +#if SK_BUILD_FOR_WIN32 + LARGE_INTEGER currentTime; + LARGE_INTEGER frequency; + QueryPerformanceCounter(¤tTime); + QueryPerformanceFrequency(&frequency); + return (double)currentTime.QuadPart / (double)frequency.QuadPart; +#elif _POSIX_TIMERS > 0 && defined(CLOCK_REALTIME) + struct timespec currentTime; + clock_gettime(CLOCK_REALTIME, ¤tTime); + return currentTime.tv_sec + (double)currentTime.tv_nsec / 1e9; +#elif SK_BUILD_FOR_MAC || SK_BUILD_FOR_UNIX || SK_BUILD_FOR_ANDROID + struct timeval currentTime; + gettimeofday(¤tTime, NULL); + return currentTime.tv_sec + (double)currentTime.tv_usec / 1e6; +#else + return clock() / (double)CLOCKS_PER_SEC; +#endif +} + +bool get_directory(const char path[], SkTArray<SkString>* entries) { +#if SK_BUILD_FOR_MAC || SK_BUILD_FOR_UNIX || SK_BUILD_FOR_ANDROID + // Open the directory and check for success + DIR* dir = opendir(path); + if (NULL == dir) { + return false; + } + + // Loop through dir entries until there are none left (i.e. readdir returns NULL) + struct dirent* entry; + while ((entry = readdir(dir))) { + // dirent only gives relative paths, we need to join them to the base path to check if they + // are directories. + SkString joinedPath = SkOSPath::SkPathJoin(path, entry->d_name); + + // We only care about files + if (!sk_isdir(joinedPath.c_str())) { + entries->push_back(SkString(entry->d_name)); + } + } + + closedir(dir); + + return true; +#elif SK_BUILD_FOR_WIN32 + char pathDirGlob[MAX_PATH]; + size_t pathLength = strlen(path); + strncpy(pathDirGlob, path, pathLength); + + if (path[pathLength - 1] == '/' || path[pathLength - 1] == '\\') { + SkASSERT(pathLength + 2 <= MAX_PATH); + pathDirGlob[pathLength] = '*'; + pathDirGlob[pathLength + 1] = '\0'; + } else { + SkASSERT(pathLength + 3 <= MAX_PATH); + pathDirGlob[pathLength] = '\\'; + pathDirGlob[pathLength + 1] = '*'; + pathDirGlob[pathLength + 2] = '\0'; + } + + WIN32_FIND_DATA findFileData; + HANDLE hFind = FindFirstFile(pathDirGlob, &findFileData); + if (INVALID_HANDLE_VALUE == hFind) { + return false; + } + + do { + if ((findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + entries->push_back(SkString(findFileData.cFileName)); + } + } while (FindNextFile(hFind, &findFileData) != 0); + + FindClose(hFind); + return true; +#else + return false; +#endif +} + +bool glob_files(const char globPattern[], SkTArray<SkString>* entries) { +#if SK_BUILD_FOR_MAC || SK_BUILD_FOR_UNIX + // TODO Make sure this works on windows. This may require use of FindNextFile windows function. + glob_t globBuffer; + if (glob(globPattern, 0, NULL, &globBuffer) != 0) { + return false; + } + + // Note these paths are in sorted order by default according to http://linux.die.net/man/3/glob + // Check under the flag GLOB_NOSORT + char** paths = globBuffer.gl_pathv; + while(NULL != *paths) { + entries->push_back(SkString(*paths)); + paths++; + } + + globfree(&globBuffer); + + return true; +#else + return false; +#endif +} + +SkString get_absolute_path(const SkString& path) { +#if SK_BUILD_FOR_MAC || SK_BUILD_FOR_UNIX || SK_BUILD_FOR_ANDROID + SkString fullPath(PATH_MAX + 1); + if (realpath(path.c_str(), fullPath.writable_str()) == NULL) { + fullPath.reset(); + } + return fullPath; +#elif SK_BUILD_FOR_WIN32 + SkString fullPath(MAX_PATH); + if (_fullpath(fullPath.writable_str(), path.c_str(), MAX_PATH) == NULL) { + fullPath.reset(); + } + return fullPath; +#else + return SkString(); +#endif +} diff --git a/chromium/third_party/skia/tools/skpdiff/skpdiff_util.h b/chromium/third_party/skia/tools/skpdiff/skpdiff_util.h new file mode 100644 index 00000000000..8750bf6b54d --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/skpdiff_util.h @@ -0,0 +1,60 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef skpdiff_util_DEFINED +#define skpdiff_util_DEFINED + +#include "SkString.h" +#include "SkTArray.h" + +#if SK_SUPPORT_OPENCL +#if SK_BUILD_FOR_MAC +# include <OpenCL/cl.h> +#else +# include <CL/cl.h> +#endif + +/** + * Converts an OpenCL error number into the string of its enumeration name. + * @param err The OpenCL error number + * @return The string of the name of the error; "UNKOWN" if the error number is invalid + */ +const char* cl_error_to_string(cl_int err); +#endif + +/** + * Get a positive monotonic real-time measure of the amount of seconds since some undefined epoch. + * Maximum precision is the goal of this routine. + * @return Amount of time in seconds since some epoch + */ +double get_seconds(); + +/** + * Get file entries of the given directory. + * @param path A path to a directory to enumerate + * @param entries A vector to return the results into + * @return True on success, false otherwise + */ +bool get_directory(const char path[], SkTArray<SkString>* entries); + +/** + * Gets the files that match the specified pattern in sorted order. + * @param globPattern The pattern to use. Patterns must be valid paths, optionally with wildcards (*) + * @param entries An array to return the results into + * @return True on success, false otherwise + */ +bool glob_files(const char globPattern[], SkTArray<SkString>* entries); + +/** + * Gets the absolute version of the given path. + * @param path The absolute or relative path to expand + * @return The absolute path of the given path on success, or an empty string on failure. + */ +SkString get_absolute_path(const SkString& path); + + +#endif diff --git a/chromium/third_party/skia/tools/skpdiff/viewer.html b/chromium/third_party/skia/tools/skpdiff/viewer.html new file mode 100644 index 00000000000..6ae65f756c5 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/viewer.html @@ -0,0 +1,81 @@ +<!doctype html> +<!-- This whole page uses the module --> +<html ng-app="diff_viewer"> + <head> + <script type="text/javascript" + src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script> + <script type="text/javascript" src="skpdiff_output.json"></script> + <script type="text/javascript" src="diff_viewer.js"></script> + <link rel="stylesheet" type="text/css" href="viewer_style.css"> + <title>SkPDiff</title> + </head> + <body ng-controller="DiffListController" ng-class="statusClass"> + <!-- The commit button --> + <div ng-show="isDynamic" class="commit"> + <button ng-click="commitRebaselines()">Commit</button> + </div> + <!-- Give a choice of how to order the differs --> + <div style="margin:8px"> + Show me the worst by metric: + <button ng-repeat="differ in differs" ng-click="setSortIndex($index)"> + <span class="result-{{ $index }}" style="padding-left:12px;"> </span> + {{ differ.title }} + </button> + </div> + <!-- Begin list of differences --> + <table> + <thead> + <tr> + <td ng-show="isDynamic">Rebaseline?</td> + <td>Name</td> + <td>Difference Mask</td> + <td>Expected Image</td> + <td>Actual Image</td> + <td>Results</td> + </tr> + </thead> + <tbody> + <!-- + Loops through every record and crates a row for it. This sorts based on the whichever + metric the user chose, and places a limit on the max number of records to show. + --> + <tr ng-repeat="record in records | orderBy:sortingDiffer | limitTo:500" + ng-class="{selected: record.isRebaselined}" + ng-controller="ImageController"> + <td ng-show="isDynamic"> + <input type="checkbox" + ng-model="record.isRebaselined" + ng-click="selectedRebaseline($index, $event)" + ng-class="{lastselected: lastSelectedIndex == $index}" /> + </td> + <td class="common-name">{{ record.commonName }}</td> + <td> + <img-compare type="alphaMask" + class="left-image" + ng-mousedown="MagnifyDraw($event, true)" + ng-mousemove="MagnifyDraw($event, false)" + ng-mouseup="MagnifyEnd($event)" + ng-mouseleave="MagnifyEnd($event)"/> + </td> + <td> + <img-compare type="baseline" + name="{{record.commonName}}" + class="gm-image left-image" /> + </td> + <td> + <img-compare type="test" + name="{{record.commonName}}" + class="gm-image right-image" /> + </td> + <td> + <div ng-repeat="diff in record.diffs" + ng-mouseover="highlight(diff.differName)" + class="result result-{{$index}}"> + <span class="result-button">{{ diff.result }}</span> + </div> + </td> + </tr> + </tbody> + </table> + </body> +</html> diff --git a/chromium/third_party/skia/tools/skpdiff/viewer_style.css b/chromium/third_party/skia/tools/skpdiff/viewer_style.css new file mode 100644 index 00000000000..1e4420fe850 --- /dev/null +++ b/chromium/third_party/skia/tools/skpdiff/viewer_style.css @@ -0,0 +1,173 @@ +body, img, div { + font-family: Verdana; + margin: 0; + padding: 0; +} + +table { + width: auto; + border-collapse: collapse; + border-spacing: 0; + padding: 8px; +} + +td { + border-top: 1px solid #DDD; + padding: 16px; +} +thead > tr > td { + border: none; +} + +button { + font-family: Verdana; + font-weight: 900; +} + +input[type="checkbox"] { + -webkit-appearance: none; + -moz-appearance: none; + width: 20px; + height:20px; + padding: 2px; + background: #EEE; + border-radius: 2px; + box-shadow: inset 0 0 8px -2px black; +} + +input[type="checkbox"]:checked { + background: #a9db80; + background: -webkit-linear-gradient(top, #00b7ea 0%,#009ec3 100%); +} + +input[type="checkbox"]:active { + background: #a9db80; + background: -webkit-linear-gradient(top, #009ec3 0%,#00b7ea 100%); +} + +input[type="checkbox"].lastselected { + padding: 0; + border: 2px solid #009ec3; + box-shadow: inset 0 0 8px -2px black, 0 0 6px black; +} + +.commit { + position: absolute; + top: 8px; + right: 8px; +} + +.commit > button { + border: 1px solid #00687F; + border-radius: 4px; + padding: 8px; + color: white; + text-shadow: 0 0 4px black; + box-shadow: 0 0 8px black; + background: #a9db80; + background: -webkit-linear-gradient(top, #a9db80 0%,#96c56f 100%); +} + +.commit > button:active { + background: #96c56f; + background: -webkit-linear-gradient(top, #96c56f 0%,#a9db80 100%); +} + +.common-name { + vertical-align: top; +} + + +.gm-image { + border: 1px dotted black; +} + +.gm-image:hover { + border: 1px dashed black; +} + +.selected { + background: #ffff88; +} + +.left-image { + float: right; +} + +.right-image { + text-align: right; +} + +.success-flash { + -webkit-animation-duration: 0.5s; + -webkit-animation-name: greenflash; +} + +.failure-flash { + -webkit-animation-duration: 0.8s; + -webkit-animation-name: redflash; +} + +@-webkit-keyframes greenflash { + from { + background-color: #8F8; + } + + to { + background-color: #FFF + } +} + +@-webkit-keyframes redflash { + from { + background-color: #F88; + } + + to { + background-color: #FFF + } +} + +.result { + padding: 8px; + cursor: default; + -webkit-filter: grayscale(30%) +} + +.result:hover { + border: 2px dotted #DDD; + padding: 6px; + -webkit-filter: grayscale(0) +} + +.result-0 { + background-color: #268bd2; +} + +.result-1 { + background-color: #d33682; +} + +.result-2 { + background-color: #b58900; +} + +.result-3 { + background-color: #cb4b16; +} + +.result-4 { + background-color: #6c71c4; +} + +.result-5 { + background-color: #dc322f; +} + +.result-6 { + background-color: #2aa198; +} + +.result-7 { + background-color: #859900; +} diff --git a/chromium/third_party/skia/tools/skpinfo.cpp b/chromium/third_party/skia/tools/skpinfo.cpp new file mode 100644 index 00000000000..6b5eded2b38 --- /dev/null +++ b/chromium/third_party/skia/tools/skpinfo.cpp @@ -0,0 +1,162 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkCommandLineFlags.h" +#include "SkPicture.h" +#include "SkPicturePlayback.h" +#include "SkStream.h" + +DEFINE_string2(input, i, "", "skp on which to report"); +DEFINE_bool2(version, v, true, "version"); +DEFINE_bool2(width, w, true, "width"); +DEFINE_bool2(height, h, true, "height"); +DEFINE_bool2(flags, f, true, "flags"); +DEFINE_bool2(tags, t, true, "tags"); +DEFINE_bool2(quiet, q, false, "quiet"); + +// This tool can print simple information about an SKP but its main use +// is just to check if an SKP has been truncated during the recording +// process. +// return codes: +static const int kSuccess = 0; +static const int kTruncatedFile = 1; +static const int kNotAnSKP = 2; +static const int kInvalidTag = 3; +static const int kMissingInput = 4; +static const int kIOError = 5; + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + SkCommandLineFlags::SetUsage("Prints information about an skp file"); + SkCommandLineFlags::Parse(argc, argv); + + if (FLAGS_input.count() != 1) { + if (!FLAGS_quiet) { + SkDebugf("Missing input file\n"); + } + return kMissingInput; + } + + SkFILEStream stream(FLAGS_input[0]); + if (!stream.isValid()) { + if (!FLAGS_quiet) { + SkDebugf("Couldn't open file\n"); + } + return kIOError; + } + + size_t totStreamSize = stream.getLength(); + + SkPictInfo info; + if (!SkPicture::InternalOnly_StreamIsSKP(&stream, &info)) { + return kNotAnSKP; + } + + if (FLAGS_version && !FLAGS_quiet) { + SkDebugf("Version: %d\n", info.fVersion); + } + if (FLAGS_width && !FLAGS_quiet) { + SkDebugf("Width: %d\n", info.fWidth); + } + if (FLAGS_height && !FLAGS_quiet) { + SkDebugf("Height: %d\n", info.fHeight); + } + if (FLAGS_flags && !FLAGS_quiet) { + SkDebugf("Flags: 0x%x\n", info.fFlags); + } + + if (!stream.readBool()) { + // If we read true there's a picture playback object flattened + // in the file; if false, there isn't a playback, so we're done + // reading the file. + return kSuccess; + } + + for (;;) { + uint32_t tag = stream.readU32(); + if (SK_PICT_EOF_TAG == tag) { + break; + } + + uint32_t chunkSize = stream.readU32(); + size_t curPos = stream.getPosition(); + + // "move" doesn't error out when seeking beyond the end of file + // so we need a preemptive check here. + if (curPos+chunkSize > totStreamSize) { + if (!FLAGS_quiet) { + SkDebugf("truncated file\n"); + } + return kTruncatedFile; + } + + // Not all the tags store the chunk size (in bytes). Three + // of them store tag-specific size information (e.g., number of + // fonts) instead. This forces us to early exit when those + // chunks are encountered. + switch (tag) { + case SK_PICT_READER_TAG: + if (FLAGS_tags && !FLAGS_quiet) { + SkDebugf("SK_PICT_READER_TAG %d\n", chunkSize); + } + break; + case SK_PICT_FACTORY_TAG: + if (FLAGS_tags && !FLAGS_quiet) { + SkDebugf("SK_PICT_FACTORY_TAG %d\n", chunkSize); + } + // Remove this code when v21 and below are no longer supported +#ifndef DISABLE_V21_COMPATIBILITY_CODE + if (info.fVersion < 22) { + if (!FLAGS_quiet) { + SkDebugf("Exiting early due to format limitations\n"); + } + return kSuccess; // TODO: need to store size in bytes + } +#endif + break; + case SK_PICT_TYPEFACE_TAG: + if (FLAGS_tags && !FLAGS_quiet) { + SkDebugf("SK_PICT_TYPEFACE_TAG %d\n", chunkSize); + SkDebugf("Exiting early due to format limitations\n"); + } + return kSuccess; // TODO: need to store size in bytes + break; + case SK_PICT_PICTURE_TAG: + if (FLAGS_tags && !FLAGS_quiet) { + SkDebugf("SK_PICT_PICTURE_TAG %d\n", chunkSize); + SkDebugf("Exiting early due to format limitations\n"); + } + return kSuccess; // TODO: need to store size in bytes + break; + case SK_PICT_BUFFER_SIZE_TAG: + if (FLAGS_tags && !FLAGS_quiet) { + SkDebugf("SK_PICT_BUFFER_SIZE_TAG %d\n", chunkSize); + } + break; + default: + if (!FLAGS_quiet) { + SkDebugf("Unknown tag %d\n", chunkSize); + } + return kInvalidTag; + } + + if (!stream.move(chunkSize)) { + if (!FLAGS_quiet) { + SkDebugf("seek error\n"); + } + return kTruncatedFile; + } + } + + return kSuccess; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/skpmaker.cpp b/chromium/third_party/skia/tools/skpmaker.cpp new file mode 100644 index 00000000000..390e5ca299a --- /dev/null +++ b/chromium/third_party/skia/tools/skpmaker.cpp @@ -0,0 +1,85 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Simple tool to generate SKP files for testing. + */ + +#include "SkCanvas.h" +#include "SkColor.h" +#include "SkCommandLineFlags.h" +#include "SkPaint.h" +#include "SkPicture.h" +#include "SkPictureRecorder.h" +#include "SkScalar.h" +#include "SkStream.h" + +// Flags used by this file, alphabetically: +DEFINE_int32(blue, 128, "Value of blue color channel in image, 0-255."); +DEFINE_int32(border, 4, "Width of the black border around the image."); +DEFINE_int32(green, 128, "Value of green color channel in image, 0-255."); +DEFINE_int32(height, 200, "Height of canvas to create."); +DEFINE_int32(red, 128, "Value of red color channel in image, 0-255."); +DEFINE_int32(width, 300, "Width of canvas to create."); +DEFINE_string(writePath, "", "Filepath to write the SKP into."); + +static void skpmaker(int width, int height, int border, SkColor color, + const char *writePath) { + SkPictureRecorder recorder; + SkCanvas* canvas = recorder.beginRecording(width, height, NULL, 0); + SkPaint paint; + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(SK_ColorBLACK); + canvas->drawRectCoords(0, 0, SkIntToScalar(width), SkIntToScalar(height), paint); + paint.setColor(color); + canvas->drawRectCoords(SkIntToScalar(border), SkIntToScalar(border), + SkIntToScalar(width - border*2), SkIntToScalar(height - border*2), + paint); + SkAutoTUnref<SkPicture> pict(recorder.endRecording()); + SkFILEWStream stream(writePath); + pict->serialize(&stream); +} + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + SkCommandLineFlags::SetUsage("Creates a simple .skp file for testing."); + SkCommandLineFlags::Parse(argc, argv); + + // Validate flags. + if ((FLAGS_blue < 0) || (FLAGS_blue > 255)) { + SkDebugf("--blue must be within range [0,255]\n"); + exit(-1); + } + if ((FLAGS_green < 0) || (FLAGS_green > 255)) { + SkDebugf("--green must be within range [0,255]\n"); + exit(-1); + } + if (FLAGS_height <= 0) { + SkDebugf("--height must be >0\n"); + exit(-1); + } + if ((FLAGS_red < 0) || (FLAGS_red > 255)) { + SkDebugf("--red must be within range [0,255]\n"); + exit(-1); + } + if (FLAGS_width <= 0) { + SkDebugf("--width must be >0\n"); + exit(-1); + } + if (FLAGS_writePath.isEmpty()) { + SkDebugf("--writePath must be nonempty\n"); + exit(-1); + } + + SkColor color = SkColorSetRGB(FLAGS_red, FLAGS_green, FLAGS_blue); + skpmaker(FLAGS_width, FLAGS_height, FLAGS_border, color, FLAGS_writePath[0]); + return 0; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/submit_try b/chromium/third_party/skia/tools/submit_try new file mode 100755 index 00000000000..969fdfcd570 --- /dev/null +++ b/chromium/third_party/skia/tools/submit_try @@ -0,0 +1,294 @@ +#!/usr/bin/python + +# Copyright (c) 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +""" +submit_try: Submit a try request. + +This is a thin wrapper around the try request utilities in depot_tools which +adds some validation and supports both git and svn. +""" + + +import httplib +import json +import os +import re +import shutil +import subprocess +import svn +import sys +import tempfile + +import retrieve_from_googlesource + + +# Alias which can be used to run a try on every builder. +ALL_BUILDERS = 'all' +# Alias which can be used to run a try on all compile builders. +COMPILE_BUILDERS = 'compile' +# Alias which can be used to run a try on all builders that are run in the CQ. +CQ_BUILDERS = 'cq' +# Alias which can be used to specify a regex to choose builders. +REGEX = 'regex' + +ALL_ALIASES = [ALL_BUILDERS, COMPILE_BUILDERS, REGEX, CQ_BUILDERS] + +LARGE_NUMBER_OF_BOTS = 5 + +GIT = 'git.bat' if os.name == 'nt' else 'git' + +# URL of the slaves.cfg file in the Skia buildbot sources. +SKIA_REPO = 'https://skia.googlesource.com/buildbot' +SLAVES_CFG_PATH = 'master/slaves.cfg' + +# All try builders have this suffix. +TRYBOT_SUFFIX = '-Trybot' + +# String for matching the svn url of the try server inside codereview.settings. +TRYSERVER_SVN_URL = 'TRYSERVER_SVN_URL: ' + +# Strings used for matching svn config properties. +URL_STR = 'URL' +REPO_ROOT_STR = 'Repository Root' + + +def FindDepotTools(): + """ Find depot_tools on the local machine and return its location. """ + which_cmd = 'where' if os.name == 'nt' else 'which' + cmd = [which_cmd, 'gcl'] + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if proc.wait() != 0: + raise Exception('Couldn\'t find depot_tools in PATH!') + gcl = proc.communicate()[0].split('\n')[0].rstrip() + depot_tools_dir = os.path.dirname(gcl) + return depot_tools_dir + + +def GetCheckoutRoot(): + """ Determine where the local checkout is rooted.""" + cmd = ['git', 'rev-parse', '--show-toplevel'] + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + if proc.wait() != 0: + raise Exception('Couldn\'t find checkout root!') + return os.path.basename(proc.communicate()[0]) + + +def GetTryRepo(): + """Determine the TRYSERVER_SVN_URL from the codereview.settings file.""" + codereview_settings_file = os.path.join(os.path.dirname(__file__), os.pardir, + 'codereview.settings') + with open(codereview_settings_file) as f: + for line in f: + if line.startswith(TRYSERVER_SVN_URL): + return line[len(TRYSERVER_SVN_URL):].rstrip() + raise Exception('Couldn\'t determine the TRYSERVER_SVN_URL. Make sure it is ' + 'defined in the %s file.' % codereview_settings_file) + + +def RetrieveTrybotList(): + """Retrieve the list of known trybots from the checked-in buildbot + configuration.""" + # Retrieve the slaves.cfg file from the repository. + slaves_cfg_text = retrieve_from_googlesource.get(SKIA_REPO, SLAVES_CFG_PATH) + + # Execute the slaves.cfg file to obtain the list of slaves. + vars = {} + exec(slaves_cfg_text, vars) + slaves_cfg = vars['slaves'] + + # Pull the list of known builders from the slaves list. + trybots = set() + for slave in slaves_cfg: + for builder in slave['builder']: + if not builder.endswith(TRYBOT_SUFFIX): + trybots.add(builder) + + return list(trybots), vars['cq_trybots'] + + +def ValidateArgs(argv, trybots, cq_trybots, is_svn=True): + """ Parse and validate command-line arguments. If the arguments are valid, + returns a tuple of (<changelist name>, <list of trybots>). + + trybots: list of strings; A list of the known try builders. + cq_trybots: list of strings; Trybots who get run by the commit queue. + is_svn: bool; whether or not we're in an svn checkout. + """ + + class CollectedArgs(object): + def __init__(self, bots, changelist, revision): + self._bots = bots + self._changelist = changelist + self._revision = revision + + @property + def bots(self): + for bot in self._bots: + yield bot + + @property + def changelist(self): + return self._changelist + + @property + def revision(self): + return self._revision + + usage = ( +"""submit_try: Submit a try request. +submit_try %s--bot <buildername> [<buildername> ...] + +-b, --bot Builder(s) or Alias on which to run the try. Required. + Allowed aliases: %s +-h, --help Show this message. +-r <revision#> Revision from which to run the try. +-l, --list_bots List the available try builders and aliases and exit. +""" % ('<changelist> ' if is_svn else '', ALL_ALIASES)) + + def Error(msg=None): + if msg: + print msg + print usage + sys.exit(1) + + using_bots = None + changelist = None + revision = None + + while argv: + arg = argv.pop(0) + if arg == '-h' or arg == '--help': + Error() + elif arg == '-l' or arg == '--list_bots': + format_args = ['\n '.join(sorted(trybots))] + \ + ALL_ALIASES + \ + ['\n '.join(sorted(cq_trybots))] + print ( +""" +submit_try: Available builders:\n %s + +Can also use the following aliases to run on groups of builders- + %s: Will run against all trybots. + %s: Will run against all compile trybots. + %s: You will be prompted to enter a regex to select builders with. + %s: Will run against the same trybots as the commit queue:\n %s + +""" % tuple(format_args)) + sys.exit(0) + elif arg == '-b' or arg == '--bot': + if using_bots: + Error('--bot specified multiple times.') + if len(argv) < 1: + Error('You must specify a builder with "--bot".') + using_bots = [] + while argv and not argv[0].startswith('-'): + for bot in argv.pop(0).split(','): + if bot in ALL_ALIASES: + if using_bots: + Error('Cannot specify "%s" with additional builder names or ' + 'aliases.' % bot) + elif bot == COMPILE_BUILDERS: + using_bots = [t for t in trybots if t.startswith('Build')] + elif bot == CQ_BUILDERS: + using_bots = cq_trybots + elif bot == REGEX: + while True: + regex = raw_input("Enter your trybot regex: ") + p = re.compile(regex) + using_bots = [t for t in trybots if p.match(t)] + print '\n\nTrybots that match your regex:\n%s\n\n' % '\n'.join( + using_bots) + if raw_input('Re-enter regex? [y,n]: ') == 'n': + break + break + else: + if not bot in trybots: + Error('Unrecognized builder: %s' % bot) + using_bots.append(bot) + elif arg == '-r': + if len(argv) < 1: + Error('You must specify a revision with "-r".') + revision = argv.pop(0) + else: + if changelist or not is_svn: + Error('Unknown argument: %s' % arg) + changelist = arg + if is_svn and not changelist: + Error('You must specify a changelist name.') + if not using_bots: + Error('You must specify one or more builders using --bot.') + if len(using_bots) > LARGE_NUMBER_OF_BOTS: + are_you_sure = raw_input('Running a try on a large number of bots is very ' + 'expensive. You may be able to get enough ' + 'information by running on a smaller set of bots. ' + 'Are you sure you want to do this? [y,n]: ') + if are_you_sure != 'y': + Error() + return CollectedArgs(bots=using_bots, changelist=changelist, + revision=revision) + + +def SubmitTryRequest(trybots, revision=None): + """ Submits a try request on the given list of trybots. + + Args: + trybots: list of strings; the names of the try builders to run. + revision: optional string; the revision from which to run the try. + """ + botlist = ','.join(['%s%s' % (bot, TRYBOT_SUFFIX) for bot in trybots]) + # Find depot_tools. This is needed to import git_cl and trychange. + sys.path.append(FindDepotTools()) + import git_cl + import trychange + + cmd = [GIT, 'diff', git_cl.Changelist().GetUpstreamBranch(), + '--no-ext-diff'] + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + git_data = proc.communicate() + if git_data[0] is None: + raise Exception('Failed to capture git diff!') + + temp_dir = tempfile.mkdtemp() + try: + diff_file = os.path.join(temp_dir, 'patch.diff') + with open(diff_file, 'wb') as f: + f.write(git_data[0]) + f.close() + + try_args = ['--use_svn', + '--svn_repo', GetTryRepo(), + '--root', GetCheckoutRoot(), + '--bot', botlist, + '--diff', diff_file, + ] + if revision: + try_args.extend(['-r', revision]) + + # Submit the try request. + trychange.TryChange(try_args, None, False) + finally: + shutil.rmtree(temp_dir) + + +def main(): + # Retrieve the list of active try builders from the build master. + trybots, cq_trybots = RetrieveTrybotList() + + # Determine if we're in an SVN checkout. + is_svn = os.path.isdir('.svn') + + # Parse and validate the command-line arguments. + args = ValidateArgs(sys.argv[1:], trybots=trybots, cq_trybots=cq_trybots, + is_svn=is_svn) + + # Submit the try request. + SubmitTryRequest(args.bots, args.revision) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/chromium/third_party/skia/tools/submit_try.bat b/chromium/third_party/skia/tools/submit_try.bat new file mode 100644 index 00000000000..d1d5392868b --- /dev/null +++ b/chromium/third_party/skia/tools/submit_try.bat @@ -0,0 +1 @@ +python tools\submit_try %*
\ No newline at end of file diff --git a/chromium/third_party/skia/tools/svn.py b/chromium/third_party/skia/tools/svn.py new file mode 100644 index 00000000000..7927258a07a --- /dev/null +++ b/chromium/third_party/skia/tools/svn.py @@ -0,0 +1,195 @@ +''' +Copyright 2011 Google Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +''' + +import fnmatch +import os +import re +import subprocess +import threading + +PROPERTY_MIMETYPE = 'svn:mime-type' + +# Status types for GetFilesWithStatus() +STATUS_ADDED = 0x01 +STATUS_DELETED = 0x02 +STATUS_MODIFIED = 0x04 +STATUS_NOT_UNDER_SVN_CONTROL = 0x08 + + +if os.name == 'nt': + SVN = 'svn.bat' +else: + SVN = 'svn' + + +def Cat(svn_url): + """Returns the contents of the file at the given svn_url. + + @param svn_url URL of the file to read + """ + proc = subprocess.Popen([SVN, 'cat', svn_url], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + exitcode = proc.wait() + if not exitcode == 0: + raise Exception('Could not retrieve %s. Verify that the URL is valid ' + 'and check your connection.' % svn_url) + return proc.communicate()[0] + + +class Svn: + + def __init__(self, directory): + """Set up to manipulate SVN control within the given directory. + + The resulting object is thread-safe: access to all methods is + synchronized (if one thread is currently executing any of its methods, + all other threads must wait before executing any of its methods). + + @param directory + """ + self._directory = directory + # This must be a reentrant lock, so that it can be held by both + # _RunCommand() and (some of) the methods that call it. + self._rlock = threading.RLock() + + def _RunCommand(self, args): + """Run a command (from self._directory) and return stdout as a single + string. + + @param args a list of arguments + """ + with self._rlock: + print 'RunCommand: %s' % args + proc = subprocess.Popen(args, cwd=self._directory, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stdout, stderr) = proc.communicate() + if proc.returncode is not 0: + raise Exception('command "%s" failed in dir "%s": %s' % + (args, self._directory, stderr)) + return stdout + + def GetInfo(self): + """Run "svn info" and return a dictionary containing its output. + """ + output = self._RunCommand([SVN, 'info']) + svn_info = {} + for line in output.split('\n'): + if ':' in line: + (key, value) = line.split(':', 1) + svn_info[key.strip()] = value.strip() + return svn_info + + def Checkout(self, url, path): + """Check out a working copy from a repository. + Returns stdout as a single string. + + @param url URL from which to check out the working copy + @param path path (within self._directory) where the local copy will be + written + """ + return self._RunCommand([SVN, 'checkout', url, path]) + + def Update(self, path, revision='HEAD'): + """Update the working copy. + Returns stdout as a single string. + + @param path path (within self._directory) within which to run + "svn update" + @param revision revision to update to + """ + return self._RunCommand([SVN, 'update', path, '--revision', revision]) + + def ListSubdirs(self, url): + """Returns a list of all subdirectories (not files) within a given SVN + url. + + @param url remote directory to list subdirectories of + """ + subdirs = [] + filenames = self._RunCommand([SVN, 'ls', url]).split('\n') + for filename in filenames: + if filename.endswith('/'): + subdirs.append(filename.strip('/')) + return subdirs + + def GetNewFiles(self): + """Return a list of files which are in this directory but NOT under + SVN control. + """ + return self.GetFilesWithStatus(STATUS_NOT_UNDER_SVN_CONTROL) + + def GetNewAndModifiedFiles(self): + """Return a list of files in this dir which are newly added or modified, + including those that are not (yet) under SVN control. + """ + return self.GetFilesWithStatus( + STATUS_ADDED | STATUS_MODIFIED | STATUS_NOT_UNDER_SVN_CONTROL) + + def GetFilesWithStatus(self, status): + """Return a list of files in this dir with the given SVN status. + + @param status bitfield combining one or more STATUS_xxx values + """ + status_types_string = '' + if status & STATUS_ADDED: + status_types_string += 'A' + if status & STATUS_DELETED: + status_types_string += 'D' + if status & STATUS_MODIFIED: + status_types_string += 'M' + if status & STATUS_NOT_UNDER_SVN_CONTROL: + status_types_string += '\?' + status_regex_string = '^[%s].....\s+(.+)$' % status_types_string + stdout = self._RunCommand([SVN, 'status']).replace('\r', '') + status_regex = re.compile(status_regex_string, re.MULTILINE) + files = status_regex.findall(stdout) + return files + + def AddFiles(self, filenames): + """Adds these files to SVN control. + + @param filenames files to add to SVN control + """ + self._RunCommand([SVN, 'add'] + filenames) + + def SetProperty(self, filenames, property_name, property_value): + """Sets a svn property for these files. + + @param filenames files to set property on + @param property_name property_name to set for each file + @param property_value what to set the property_name to + """ + if filenames: + self._RunCommand( + [SVN, 'propset', property_name, property_value] + filenames) + + def SetPropertyByFilenamePattern(self, filename_pattern, + property_name, property_value): + """Sets a svn property for all files matching filename_pattern. + + @param filename_pattern set the property for all files whose names match + this Unix-style filename pattern (e.g., '*.jpg') + @param property_name property_name to set for each file + @param property_value what to set the property_name to + """ + with self._rlock: + all_files = os.listdir(self._directory) + matching_files = sorted(fnmatch.filter(all_files, filename_pattern)) + self.SetProperty(matching_files, property_name, property_value) + + def ExportBaseVersionOfFile(self, file_within_repo, dest_path): + """Retrieves a copy of the base version (what you would get if you ran + 'svn revert') of a file within the repository. + + @param file_within_repo path to the file within the repo whose base + version you wish to obtain + @param dest_path destination to which to write the base content + """ + self._RunCommand([SVN, 'export', '--revision', 'BASE', '--force', + file_within_repo, dest_path]) diff --git a/chromium/third_party/skia/tools/svndiff.py b/chromium/third_party/skia/tools/svndiff.py new file mode 100755 index 00000000000..716d497014e --- /dev/null +++ b/chromium/third_party/skia/tools/svndiff.py @@ -0,0 +1,336 @@ +#!/usr/bin/python +''' +Copyright 2012 Google Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +''' + +''' +Generates a visual diff of all pending changes in the local SVN (or git!) +checkout. + +Launch with --help to see more information. + +TODO(epoger): Now that this tool supports either git or svn, rename it. +TODO(epoger): Fix indentation in this file (2-space indents, not 4-space). +''' + +# common Python modules +import optparse +import os +import posixpath +import re +import shutil +import subprocess +import sys +import tempfile +import urllib2 + +# Imports from within Skia +# +# We need to add the 'gm' directory, so that we can import gm_json.py within +# that directory. That script allows us to parse the actual-results.json file +# written out by the GM tool. +# Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end* +# so any dirs that are already in the PYTHONPATH will be preferred. +# +# This assumes that the 'gm' directory has been checked out as a sibling of +# the 'tools' directory containing this script, which will be the case if +# 'trunk' was checked out as a single unit. +GM_DIRECTORY = os.path.realpath( + os.path.join(os.path.dirname(os.path.dirname(__file__)), 'gm')) +if GM_DIRECTORY not in sys.path: + sys.path.append(GM_DIRECTORY) +import gm_json +import jsondiff +import svn + +USAGE_STRING = 'Usage: %s [options]' +HELP_STRING = ''' + +Generates a visual diff of all pending changes in the local SVN/git checkout. + +This includes a list of all files that have been added, deleted, or modified +(as far as SVN/git knows about). For any image modifications, pixel diffs will +be generated. + +''' + +IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) + +TRUNK_PATH = os.path.join(os.path.dirname(__file__), os.pardir) + +OPTION_DEST_DIR = '--dest-dir' +OPTION_PATH_TO_SKDIFF = '--path-to-skdiff' +OPTION_SOURCE_DIR = '--source-dir' + +def RunCommand(command): + """Run a command, raising an exception if it fails. + + @param command the command as a single string + """ + print 'running command [%s]...' % command + retval = os.system(command) + if retval is not 0: + raise Exception('command [%s] failed' % command) + +def FindPathToSkDiff(user_set_path=None): + """Return path to an existing skdiff binary, or raise an exception if we + cannot find one. + + @param user_set_path if None, the user did not specify a path, so look in + some likely places; otherwise, only check at this path + """ + if user_set_path is not None: + if os.path.isfile(user_set_path): + return user_set_path + raise Exception('unable to find skdiff at user-set path %s' % + user_set_path) + trunk_path = os.path.join(os.path.dirname(__file__), os.pardir) + + extension = '' + if os.name is 'nt': + extension = '.exe' + + possible_paths = [os.path.join(trunk_path, 'out', 'Release', + 'skdiff' + extension), + os.path.join(trunk_path, 'out', 'Debug', + 'skdiff' + extension)] + for try_path in possible_paths: + if os.path.isfile(try_path): + return try_path + raise Exception('cannot find skdiff in paths %s; maybe you need to ' + 'specify the %s option or build skdiff?' % ( + possible_paths, OPTION_PATH_TO_SKDIFF)) + +def _DownloadUrlToFile(source_url, dest_path): + """Download source_url, and save its contents to dest_path. + Raises an exception if there were any problems.""" + try: + reader = urllib2.urlopen(source_url) + writer = open(dest_path, 'wb') + writer.write(reader.read()) + writer.close() + except BaseException as e: + raise Exception( + '%s: unable to download source_url %s to dest_path %s' % ( + e, source_url, dest_path)) + +def _CreateGSUrl(imagename, hash_type, hash_digest): + """Return the HTTP URL we can use to download this particular version of + the actually-generated GM image with this imagename. + + imagename: name of the test image, e.g. 'perlinnoise_msaa4.png' + hash_type: string indicating the hash type used to generate hash_digest, + e.g. gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5 + hash_digest: the hash digest of the image to retrieve + """ + return gm_json.CreateGmActualUrl( + test_name=IMAGE_FILENAME_RE.match(imagename).group(1), + hash_type=hash_type, + hash_digest=hash_digest) + +def _CallJsonDiff(old_json_path, new_json_path, + old_flattened_dir, new_flattened_dir, + filename_prefix): + """Using jsondiff.py, write the images that differ between two GM + expectations summary files (old and new) into old_flattened_dir and + new_flattened_dir. + + filename_prefix: prefix to prepend to filenames of all images we write + into the flattened directories + """ + json_differ = jsondiff.GMDiffer() + diff_dict = json_differ.GenerateDiffDict(oldfile=old_json_path, + newfile=new_json_path) + print 'Downloading %d before-and-after image pairs...' % len(diff_dict) + for (imagename, results) in diff_dict.iteritems(): + # TODO(epoger): Currently, this assumes that all images have been + # checksummed using gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5 + + old_checksum = results['old'] + if old_checksum: + old_image_url = _CreateGSUrl( + imagename=imagename, + hash_type=gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5, + hash_digest=old_checksum) + _DownloadUrlToFile( + source_url=old_image_url, + dest_path=os.path.join(old_flattened_dir, + filename_prefix + imagename)) + + new_checksum = results['new'] + if new_checksum: + new_image_url = _CreateGSUrl( + imagename=imagename, + hash_type=gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5, + hash_digest=new_checksum) + _DownloadUrlToFile( + source_url=new_image_url, + dest_path=os.path.join(new_flattened_dir, + filename_prefix + imagename)) + +def _RunCommand(args): + """Run a command (from self._directory) and return stdout as a single + string. + + @param args a list of arguments + """ + proc = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stdout, stderr) = proc.communicate() + if proc.returncode is not 0: + raise Exception('command "%s" failed: %s' % (args, stderr)) + return stdout + +def _GitGetModifiedFiles(): + """Returns a list of locally modified files within the current working dir. + + TODO(epoger): Move this into a git utility package? + """ + return _RunCommand(['git', 'ls-files', '-m']).splitlines() + +def _GitExportBaseVersionOfFile(file_within_repo, dest_path): + """Retrieves a copy of the base version of a file within the repository. + + @param file_within_repo path to the file within the repo whose base + version you wish to obtain + @param dest_path destination to which to write the base content + + TODO(epoger): Move this into a git utility package? + """ + # TODO(epoger): Replace use of "git show" command with lower-level git + # commands? senorblanco points out that "git show" is a "porcelain" + # command, intended for human use, as opposed to the "plumbing" commands + # generally more suitable for scripting. (See + # http://git-scm.com/book/en/Git-Internals-Plumbing-and-Porcelain ) + # + # For now, though, "git show" is the most straightforward implementation + # I could come up with. I tried using "git cat-file", but I had trouble + # getting it to work as desired. + # Note that git expects / rather than \ as a path separator even on + # windows. + args = ['git', 'show', posixpath.join('HEAD:.', file_within_repo)] + with open(dest_path, 'wb') as outfile: + proc = subprocess.Popen(args, stdout=outfile) + proc.communicate() + if proc.returncode is not 0: + raise Exception('command "%s" failed' % args) + +def SvnDiff(path_to_skdiff, dest_dir, source_dir): + """Generates a visual diff of all pending changes in source_dir. + + @param path_to_skdiff + @param dest_dir existing directory within which to write results + @param source_dir + """ + # Validate parameters, filling in default values if necessary and possible. + path_to_skdiff = os.path.abspath(FindPathToSkDiff(path_to_skdiff)) + if not dest_dir: + dest_dir = tempfile.mkdtemp() + dest_dir = os.path.abspath(dest_dir) + + os.chdir(source_dir) + svn_repo = svn.Svn('.') + using_svn = True + try: + svn_repo.GetInfo() + except: + using_svn = False + + # Prepare temporary directories. + modified_flattened_dir = os.path.join(dest_dir, 'modified_flattened') + original_flattened_dir = os.path.join(dest_dir, 'original_flattened') + diff_dir = os.path.join(dest_dir, 'diffs') + for dir in [modified_flattened_dir, original_flattened_dir, diff_dir] : + shutil.rmtree(dir, ignore_errors=True) + os.mkdir(dir) + + # Get a list of all locally modified (including added/deleted) files, + # descending subdirectories. + if using_svn: + modified_file_paths = svn_repo.GetFilesWithStatus( + svn.STATUS_ADDED | svn.STATUS_DELETED | svn.STATUS_MODIFIED) + else: + modified_file_paths = _GitGetModifiedFiles() + + # For each modified file: + # 1. copy its current contents into modified_flattened_dir + # 2. copy its original contents into original_flattened_dir + for modified_file_path in modified_file_paths: + if modified_file_path.endswith('.json'): + # Special handling for JSON files, in the hopes that they + # contain GM result summaries. + original_file = tempfile.NamedTemporaryFile(delete = False) + original_file.close() + if using_svn: + svn_repo.ExportBaseVersionOfFile( + modified_file_path, original_file.name) + else: + _GitExportBaseVersionOfFile( + modified_file_path, original_file.name) + modified_dir = os.path.dirname(modified_file_path) + platform_prefix = (re.sub(re.escape(os.sep), '__', + os.path.splitdrive(modified_dir)[1]) + + '__') + _CallJsonDiff(old_json_path=original_file.name, + new_json_path=modified_file_path, + old_flattened_dir=original_flattened_dir, + new_flattened_dir=modified_flattened_dir, + filename_prefix=platform_prefix) + os.remove(original_file.name) + else: + dest_filename = re.sub(re.escape(os.sep), '__', modified_file_path) + # If the file had STATUS_DELETED, it won't exist anymore... + if os.path.isfile(modified_file_path): + shutil.copyfile(modified_file_path, + os.path.join(modified_flattened_dir, + dest_filename)) + if using_svn: + svn_repo.ExportBaseVersionOfFile( + modified_file_path, + os.path.join(original_flattened_dir, dest_filename)) + else: + _GitExportBaseVersionOfFile( + modified_file_path, + os.path.join(original_flattened_dir, dest_filename)) + + # Run skdiff: compare original_flattened_dir against modified_flattened_dir + RunCommand('%s %s %s %s' % (path_to_skdiff, original_flattened_dir, + modified_flattened_dir, diff_dir)) + print '\nskdiff results are ready in file://%s/index.html' % diff_dir + +def RaiseUsageException(): + raise Exception('%s\nRun with --help for more detail.' % ( + USAGE_STRING % __file__)) + +def Main(options, args): + """Allow other scripts to call this script with fake command-line args. + """ + num_args = len(args) + if num_args != 0: + RaiseUsageException() + SvnDiff(path_to_skdiff=options.path_to_skdiff, dest_dir=options.dest_dir, + source_dir=options.source_dir) + +if __name__ == '__main__': + parser = optparse.OptionParser(USAGE_STRING % '%prog' + HELP_STRING) + parser.add_option(OPTION_DEST_DIR, + action='store', type='string', default=None, + help='existing directory within which to write results; ' + 'if not set, will create a temporary directory which ' + 'will remain in place after this script completes') + parser.add_option(OPTION_PATH_TO_SKDIFF, + action='store', type='string', default=None, + help='path to already-built skdiff tool; if not set, ' + 'will search for it in typical directories near this ' + 'script') + parser.add_option(OPTION_SOURCE_DIR, + action='store', type='string', + default=os.path.join('expectations', 'gm'), + help='root directory within which to compare all ' + + 'files; defaults to "%default"') + (options, args) = parser.parse_args() + Main(options, args) diff --git a/chromium/third_party/skia/tools/test_all.py b/chromium/third_party/skia/tools/test_all.py new file mode 100755 index 00000000000..6467a2160be --- /dev/null +++ b/chromium/third_party/skia/tools/test_all.py @@ -0,0 +1,32 @@ +#!/usr/bin/python + +""" +Copyright 2014 Google Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. + +Run all unittests within this directory tree, recursing into subdirectories. +""" + +import os +import unittest + +from tests import skimage_self_test + + +def main(): + # First, run any tests that cannot be automatically discovered (because + # they don't use Python's unittest framework). + skimage_self_test.main() + + # Now discover/run all tests that use Python's unittest framework. + suite = unittest.TestLoader().discover(os.path.dirname(__file__), + pattern='*_test.py') + results = unittest.TextTestRunner(verbosity=2).run(suite) + print repr(results) + if not results.wasSuccessful(): + raise Exception('failed one or more unittests') + +if __name__ == '__main__': + main() diff --git a/chromium/third_party/skia/tools/test_gpuveto.py b/chromium/third_party/skia/tools/test_gpuveto.py new file mode 100755 index 00000000000..fbb29aefc6f --- /dev/null +++ b/chromium/third_party/skia/tools/test_gpuveto.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python + +# Copyright 2014 Google Inc. +# +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Script to test out suitableForGpuRasterization (via gpuveto)""" + +import argparse +import glob +import os +import re +import subprocess +import sys + +# Set the PYTHONPATH to include the tools directory. +sys.path.append( + os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir)) +import find_run_binary + +def list_files(dir_or_file): + """Returns a list of all the files from the provided argument + + @param dir_or_file: either a directory or skp file + + @returns a list containing the files in the directory or a single file + """ + files = [] + for globbedpath in glob.iglob(dir_or_file): # useful on win32 + if os.path.isdir(globbedpath): + for filename in os.listdir(globbedpath): + newpath = os.path.join(globbedpath, filename) + if os.path.isfile(newpath): + files.append(newpath) + elif os.path.isfile(globbedpath): + files.append(globbedpath) + return files + + +def execute_program(args): + """Executes a process and waits for it to complete. + + @param args: is passed into subprocess.Popen(). + + @returns a tuple of the process output (returncode, output) + """ + proc = subprocess.Popen(args, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + output, _ = proc.communicate() + errcode = proc.returncode + return (errcode, output) + + +class GpuVeto(object): + + def __init__(self): + self.bench_pictures = find_run_binary.find_path_to_program( + 'bench_pictures') + sys.stdout.write('Running: %s\n' % (self.bench_pictures)) + self.gpuveto = find_run_binary.find_path_to_program('gpuveto') + assert os.path.isfile(self.bench_pictures) + assert os.path.isfile(self.gpuveto) + self.indeterminate = 0 + self.truePositives = 0 + self.falsePositives = 0 + self.trueNegatives = 0 + self.falseNegatives = 0 + + def process_skps(self, dir_or_file): + for skp in enumerate(dir_or_file): + self.process_skp(skp[1]) + + sys.stdout.write('TP %d FP %d TN %d FN %d IND %d\n' % (self.truePositives, + self.falsePositives, + self.trueNegatives, + self.falseNegatives, + self.indeterminate)) + + + def process_skp(self, skp_file): + assert os.path.isfile(skp_file) + #print skp_file + + # run gpuveto on the skp + args = [self.gpuveto, '-r', skp_file] + returncode, output = execute_program(args) + if (returncode != 0): + return + + if ('unsuitable' in output): + suitable = False + else: + assert 'suitable' in output + suitable = True + + # run raster config + args = [self.bench_pictures, '-r', skp_file, + '--repeat', '20', + '--timers', 'w', + '--config', '8888'] + returncode, output = execute_program(args) + if (returncode != 0): + return + + matches = re.findall('[\d]+\.[\d]+', output) + if len(matches) != 1: + return + + rasterTime = float(matches[0]) + + # run gpu config + args2 = [self.bench_pictures, '-r', skp_file, + '--repeat', '20', + '--timers', 'w', + '--config', 'gpu'] + returncode, output = execute_program(args2) + if (returncode != 0): + return + + matches = re.findall('[\d]+\.[\d]+', output) + if len(matches) != 1: + return + + gpuTime = float(matches[0]) + + # happens if page is too big it will not render + if 0 == gpuTime: + return + + tolerance = 0.05 + tol_range = tolerance * gpuTime + + + if rasterTime > gpuTime - tol_range and rasterTime < gpuTime + tol_range: + result = "NONE" + self.indeterminate += 1 + elif suitable: + if gpuTime < rasterTime: + self.truePositives += 1 + result = "TP" + else: + self.falsePositives += 1 + result = "FP" + else: + if gpuTime < rasterTime: + self.falseNegatives += 1 + result = "FN" + else: + self.trueNegatives += 1 + result = "TN" + + + sys.stdout.write('%s: gpuveto: %d raster %.2f gpu: %.2f Result: %s\n' % ( + skp_file, suitable, rasterTime, gpuTime, result)) + +def main(main_argv): + parser = argparse.ArgumentParser() + parser.add_argument('--skp_path', + help='Path to the SKP(s). Can either be a directory ' \ + 'containing SKPs or a single SKP.', + required=True) + + args = parser.parse_args() + GpuVeto().process_skps(list_files(args.skp_path)) + +if __name__ == '__main__': + sys.exit(main(sys.argv[1])) diff --git a/chromium/third_party/skia/tools/test_image_decoder.cpp b/chromium/third_party/skia/tools/test_image_decoder.cpp new file mode 100644 index 00000000000..106cf782f25 --- /dev/null +++ b/chromium/third_party/skia/tools/test_image_decoder.cpp @@ -0,0 +1,39 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkBitmap.h" +#include "SkForceLinking.h" +#include "SkGraphics.h" +#include "SkImageDecoder.h" + +__SK_FORCE_IMAGE_DECODER_LINKING; + +/** + Simple program to test Skia's ability to decode images without + errors or debug messages. */ +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { + if (argc < 2) { + SkDebugf("Usage:\n %s imagefile\n\n", argv[0]); + return 3; + } + SkAutoGraphics ag; // Enable use of SkRTConfig + SkBitmap bitmap; + if (!(SkImageDecoder::DecodeFile(argv[1], &bitmap))) { + return 2; + } + if (bitmap.empty()) { + return 1; + } + return 0; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif diff --git a/chromium/third_party/skia/tools/test_pdfs.py b/chromium/third_party/skia/tools/test_pdfs.py new file mode 100644 index 00000000000..ac3eab9a335 --- /dev/null +++ b/chromium/third_party/skia/tools/test_pdfs.py @@ -0,0 +1,60 @@ +''' +Compares the rendererings of serialized SkPictures to expected images. + +Launch with --help to see more information. + + +Copyright 2012 Google Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +''' +# common Python modules +import os +import optparse +import sys +import shutil +import tempfile +import test_rendering + +USAGE_STRING = 'Usage: %s input... expectedDir' +HELP_STRING = ''' + +Takes input SkPicture files and renders them as PDF files, and then compares +those resulting PDF files against PDF files found in expectedDir. + +Each instance of "input" can be either a file (name must end in .skp), or a +directory (in which case this script will process all .skp files within the +directory). +''' + + +def Main(args): + """Allow other scripts to call this script with fake command-line args. + + @param The commandline argument list + """ + parser = optparse.OptionParser(USAGE_STRING % '%prog' + HELP_STRING) + parser.add_option('--render_dir', dest='render_dir', + help = ('specify the location to output the rendered ' + 'files. Default is a temp directory.')) + parser.add_option('--diff_dir', dest='diff_dir', + help = ('specify the location to output the diff files. ' + 'Default is a temp directory.')) + + options, arguments = parser.parse_args(args) + + if (len(arguments) < 3): + print("Expected at least one input and one ouput folder.") + parser.print_help() + sys.exit(-1) + + inputs = arguments[1:-1] + expected_dir = arguments[-1] + + test_rendering.TestRenderSkps(inputs, expected_dir, options.render_dir, + options.diff_dir, 'render_pdfs', '') + +if __name__ == '__main__': + Main(sys.argv) + diff --git a/chromium/third_party/skia/tools/tsan.supp b/chromium/third_party/skia/tools/tsan.supp new file mode 100644 index 00000000000..f6870140404 --- /dev/null +++ b/chromium/third_party/skia/tools/tsan.supp @@ -0,0 +1,27 @@ +# Suppressions for Thread Sanitizer +# +# CAREFUL! Comments must go on their own line or your suppressions will silently fail. + +# WebP races (harmlessly) choosing function pointers for SIMD versions of some of its functions. +race:third_party/externals/libwebp + +# Poppler races on startup. +race:libpoppler.so +# LCMS is used by poppler, and also races. +race:liblcms2.so + +# skia:2459 Seemingly misdiagnosed use-after-free, having something to do with software GL drivers. +# Having trouble getting this suppression to match. +# We've tried: race:swrast_dri.so +# race:SkGLContextHelper::init +# Maybe because it's diagnosed as a use-after-free, not as a race? +race:SkGLContextHelper + +# Threadsafe, should be ported to SkLazyPtr. +race:SkFontHost_FreeType +race:is_lcd_supported + +# Not threadsafe, should be fixed. +race:RefFCI +race:SkString +race:SkPDF diff --git a/chromium/third_party/skia/tools/valgrind.supp b/chromium/third_party/skia/tools/valgrind.supp new file mode 100644 index 00000000000..2af598d22a0 --- /dev/null +++ b/chromium/third_party/skia/tools/valgrind.supp @@ -0,0 +1,202 @@ +# Pass this file to Valgrind with "--suppressions=tools/valgrind.supp" + +# Third party lib, driver issues. +{ + ati_driver_bug_1 + Memcheck:Param + ioctl(generic) + fun:ioctl + ... + obj:/usr/lib/x86_64-linux-gnu/dri/fglrx_dri.so +} +{ + ati_driver_bug_2 + Memcheck:Cond + obj:/usr/lib/x86_64-linux-gnu/dri/fglrx_dri.so +} +{ + ati_driver_bug_3 + Memcheck:Addr8 + ... + obj:/usr/lib/x86_64-linux-gnu/dri/fglrx_dri.so +} +{ + ati_driver_bug_4 + Memcheck:Addr4 + ... + obj:/usr/lib/x86_64-linux-gnu/dri/fglrx_dri.so +} +{ + ati_driver_bug_5 + Memcheck:Addr2 + ... + obj:/usr/lib/x86_64-linux-gnu/dri/fglrx_dri.so +} +{ + ati_driver_bug_6 + Memcheck:Addr1 + ... + obj:/usr/lib/x86_64-linux-gnu/dri/fglrx_dri.so +} +{ + ati_driver_bug_7 + Memcheck:Leak + fun:malloc + obj:/usr/lib/x86_64-linux-gnu/dri/fglrx_dri.so +} +{ + driver_bug_8 + Memcheck:Overlap + fun:strcpy + obj:/usr/lib/x86_64-linux-gnu/dri/fglrx_dri.so +} +{ + ati_driver_bug_9 + Memcheck:Leak + fun:calloc + obj:/usr/lib/x86_64-linux-gnu/dri/fglrx_dri.so +} +{ + ati_driver_bug_10 + Memcheck:Leak + fun:malloc + obj:/usr/lib/fglrx/fglrx-libGL.so.1.2 +} +{ + nv_driver_bug_1 + Memcheck:Param + write(buf) + ... + obj:/usr/lib/libnvidia-glcore.so* +} +{ + nv_driver_bug_2 + Memcheck:Cond + obj:/usr/lib/libnvidia-glcore.so* +} +{ + nv_driver_bug_3 + Memcheck:Leak + fun:calloc + obj:/usr/lib*/libGL.so* +} +{ + font_config_bug_1 + Memcheck:Addr4 + fun:FcConfigFileExists +} +{ + font_config_bug_2 + Memcheck:Leak + fun:malloc + fun:FcFontSetCreate +} +{ + font_config_bug_3 + Memcheck:Leak + fun:realloc + fun:FcFontSetAdd +} +{ + font_config_bug_4 + Memcheck:Leak + fun:malloc + fun:FcPatternObjectInsertElt + fun:FcPatternObjectAddWithBinding +} +{ + zlib_bug_1 + Memcheck:Cond + fun:inflateReset2 + fun:inflateInit2_ + fun:png_create_read_struct_2 + fun:png_create_read_struct +} + +# Why is it OK to suppress this? +{ + SkRTConfRegistry_bug_1 + Memcheck:Leak + fun:_Znwm + fun:_ZN16SkRTConfRegistry12registerConfEP12SkRTConfBase +} + +# The gpu_issue_* suppressions suppress issues that cannot be reproduced locally. These appear to be +# due to valgrind not knowing about memory mapped by the ATI driver via glMapBuffer. +{ + gpu_issue_1 + Memcheck:Addr2 + fun:_ZNK5GrGpu18getQuadIndexBufferEv +} +{ + gpu_issue_2 + Memcheck:Addr2 + fun:_ZN24GrAAHairLinePathRenderer6CreateEP9GrContext +} +{ + gpu_issue_3 + Memcheck:Addr2 + fun:_ZN16GrAARectRenderer21aaFillRectIndexBufferEP5GrGpu +} +{ + gpu_issue_4 + Memcheck:Addr8 + fun:_ZN24GrAAHairLinePathRenderer14createLineGeomERK6SkPathP12GrDrawTargetRK8SkTArrayI7SkPointLb1EEiPNS3_19AutoReleaseGeometryEP6SkRect +} +{ + gpu_issue_5 + Memcheck:Addr8 + fun:_ZN21GrDefaultPathRenderer10createGeomERK6SkPathRK11SkStrokeRecfP12GrDrawTargetP15GrPrimitiveTypePiSA_PNS6_19AutoReleaseGeometryE +} +{ + gpu_issue_6 + Memcheck:Addr8 + fun:_ZN22GrAAConvexPathRenderer10onDrawPathERK11SkStrokeRecP12GrDrawTargetb +} +{ + gpu_issue_7 + Memcheck:Addr4 + fun:_ZNK7SkPoint24distanceToLineBetweenSqdERKS_S1_PNS_4SideE + fun:_ZN22GrAAConvexPathRenderer10onDrawPathERK11SkStrokeRecP12GrDrawTargetb +} +{ + gpu_issue_8 + Memcheck:Addr4 + fun:_ZN24GrAAHairLinePathRenderer14createLineGeomERK6SkPathP12GrDrawTargetRK8SkTArrayI7SkPointLb1EEiPNS3_19AutoReleaseGeometryEP6SkRect +} +{ + gpu_issue_9 + Memcheck:Addr2 + fun:_ZN21GrDefaultPathRenderer10createGeomERK6SkPathRK11SkStrokeRecfP12GrDrawTargetP15GrPrimitiveTypePiSA_PNS6_19AutoReleaseGeometryE +} +{ + gpu_issue_10 + Memcheck:Addr4 + fun:_ZN22GrAAConvexPathRenderer10onDrawPathERK11SkStrokeRecP12GrDrawTargetb +} +{ + gpu_issue_11 + Memcheck:Addr2 + fun:_ZN22GrAAConvexPathRenderer10onDrawPathERK11SkStrokeRecP12GrDrawTargetb +} +{ + gpu_issue_12 + Memcheck:Addr8 + fun:_ZN22GrAAConvexPathRenderer10onDrawPathERK6SkPathRK11SkStrokeRecP12GrDrawTargetb +} +{ + gpu_issue_13 + Memcheck:Addr4 + fun:_ZNK7SkPoint24distanceToLineBetweenSqdERKS_S1_PNS_4SideE + fun:_ZN22GrAAConvexPathRenderer10onDrawPathERK6SkPathRK11SkStrokeRecP12GrDrawTargetb +} +{ + gpu_issue_14 + Memcheck:Addr4 + fun:_ZN22GrAAConvexPathRenderer10onDrawPathERK6SkPathRK11SkStrokeRecP12GrDrawTargetb +} +{ + gpu_issue_15 + Memcheck:Addr2 + fun:_ZN22GrAAConvexPathRenderer10onDrawPathERK6SkPathRK11SkStrokeRecP12GrDrawTargetb +} diff --git a/chromium/third_party/skia/tools/win_dbghelp.cpp b/chromium/third_party/skia/tools/win_dbghelp.cpp new file mode 100644 index 00000000000..eefb8ac1ce4 --- /dev/null +++ b/chromium/third_party/skia/tools/win_dbghelp.cpp @@ -0,0 +1,243 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "windows.h" +#include "win_dbghelp.h" +#include <process.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +// Remove prefix addresses. 18 = 2 * (8 digit hexa + 1 space). +// e.g. "abcd1234 abcd1234 render_pdf!processInput +#define CDB_CALLSTACK_PREFIX (18) + +// CDB.EXE prints a lot of garbage and there is no argument to pass which +// would remove all that noise. +// Using eval feature that evaluates a number in hex and prints it to stdout +// to mark where the callstack is printed. +// For example, each thread's callstack will be marked with "12340000" at +// the beginning and "12340001" at the end. +// We just made up these numbers; they could be anything, as long as they +// match up with their decimal equivalents. + +#define MARKER_THREAD_CALLSTACK_START_NUMBER "12340000" +#define MARKER_THREAD_CALLSTACK_START "Evaluate expression: 305397760 = 12340000" + +#define MARKER_THREAD_CALLSTACK_STOP_NUMBER "12340001" +#define MARKER_THREAD_CALLSTACK_STOP "Evaluate expression: 305397761 = 12340001" + +#define MARKER_EXCEPTION_CALLSTACK_START_NUMBER "12340002" +#define MARKER_EXCEPTION_CALLSTACK_START "Evaluate expression: 305397762 = 12340002" + +#define MARKER_EXCEPTION_CALLSTACK_STOP_NUMBER "12340003" +#define MARKER_EXCEPTION_CALLSTACK_STOP "Evaluate expression: 305397763 = 12340003" + +// k - print stack +// ? val - evaluate expression. Used to mark the log file. +// .ecxr - load exception, if exception was thrown. +// k - print the resolved stack by .ecxr +// q - quit cdb.exe +#define CDB_PRINT_CALLSTACK_CURRENT_THREAD "? " MARKER_THREAD_CALLSTACK_START_NUMBER "; k; ? " MARKER_THREAD_CALLSTACK_STOP_NUMBER "; .ecxr; ? " MARKER_EXCEPTION_CALLSTACK_START_NUMBER "; k; ? " MARKER_EXCEPTION_CALLSTACK_STOP_NUMBER "; q" + +static void strncpyOrSetBlank(char* dest, const char* src, size_t len) { + const char* srcOrEmptyString = (NULL == src) ? "" : src; + strncpy(dest, srcOrEmptyString, len); +} + +char debug_app_name[MAX_PATH] = ""; +void setAppName(const char* app_name) { + strncpyOrSetBlank(debug_app_name, app_name, sizeof(debug_app_name)); +} + +const char* getAppName() { + return debug_app_name; +} + +char debug_binaries_path[MAX_PATH] = ""; +void setBinariesPath(const char* binaries_path) { + strncpyOrSetBlank(debug_binaries_path, binaries_path, + sizeof(debug_binaries_path)); +} + +const char* getBinariesPath() { + return debug_binaries_path; +} + +char debug_app_version[100] = ""; +void setAppVersion(const char* version) { + strncpyOrSetBlank(debug_app_version, version, sizeof(debug_app_version)); +} + +const char* getAppVersion() { + return debug_app_version; +} + +char debug_cdb_path[MAX_PATH] = ""; +void setCdbPath(const char* path) { + strncpyOrSetBlank(debug_cdb_path, path, sizeof(debug_cdb_path)); +} + +const char* getCdbPath() { + return debug_cdb_path; +} + +/** Print all the lines of a CDB k command whicha are callstacks. + * Callstack lines are marked by start and stop markers and they are prefixed + * byt 2 hex adresses, which will not be reported. + */ +static void printCallstack(const char* filename, + const char* start, + const char* stop) { + FILE* file = fopen(filename, "rt"); + char line[1000]; + bool started = false; + // Not the most performant code, but this will be used only to collect + // the callstack from a log files, only when the application had failed. + while (fgets(line, sizeof(line), file)) { + if (!started && strncmp(start, line, strlen(start)) == 0) { + started = true; + } else if (started && strncmp(stop, line, strlen(stop)) == 0) { + break; + } else if (started) { + // Filter messages. Calstack lines contain "exe/dll!function" + if (strchr(line, '!') != NULL && strlen(line) > CDB_CALLSTACK_PREFIX) { + printf("%s", line + CDB_CALLSTACK_PREFIX); // fgets includes \n already. + } + } + } + fclose(file); +} + +#define BUILD_UNIQUE_FILENAME(var, ext, szPath, szAppName, szVersion, stLocalTime) \ + sprintf(szFileName, "%s%s\\%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld" ext, \ + szPath, szAppName, szVersion, \ + stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, \ + stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, \ + GetCurrentProcessId(), GetCurrentThreadId()); + +// Exception execution handler. Exception is recognized. Transfer control to +// the exception handler by executing the __except compound statement, +// then continue execution after the __except block. +int GenerateDumpAndPrintCallstack(EXCEPTION_POINTERS* pExceptionPointers) { + BOOL bMiniDumpSuccessful; + char szPath[MAX_PATH]; + char szFileName[MAX_PATH]; + char szFileNameOutput[MAX_PATH]; + const char* szAppName = getAppName(); + const char* szVersion = getAppVersion(); + DWORD dwBufferSize = MAX_PATH; + HANDLE hDumpFile; + SYSTEMTIME stLocalTime; + MINIDUMP_EXCEPTION_INFORMATION ExpParam; + + GetLocalTime( &stLocalTime ); + GetTempPath( dwBufferSize, szPath ); + + sprintf( szFileName, "%s%s", szPath, szAppName ); + CreateDirectory( szFileName, NULL ); + + BUILD_UNIQUE_FILENAME(szFileName, ".dmp", szPath, szAppName, szVersion, stLocalTime); + BUILD_UNIQUE_FILENAME(szFileNameOutput, ".out", szPath, szAppName, szVersion, stLocalTime); + + hDumpFile = CreateFile(szFileName, + GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_WRITE|FILE_SHARE_READ, + 0, + CREATE_ALWAYS, + 0, + 0); + + ExpParam.ThreadId = GetCurrentThreadId(); + ExpParam.ExceptionPointers = pExceptionPointers; + ExpParam.ClientPointers = TRUE; + + bMiniDumpSuccessful = MiniDumpWriteDump(GetCurrentProcess(), + GetCurrentProcessId(), + hDumpFile, + MiniDumpWithDataSegs, + &ExpParam, + NULL, + NULL); + + printf("MiniDump file: %s\n", szFileName); + printf("App exe and pdb: %s\n", getBinariesPath()); + + const char* cdbExePath = getCdbPath(); + if (cdbExePath && *cdbExePath != '\0') { + printf("Cdb exe: %s\n", cdbExePath); + + char command[MAX_PATH * 4]; + sprintf(command, "%s -y \"%s\" -i \"%s\" -z \"%s\" -c \"%s\" -kqm >\"%s\"", + cdbExePath, + getBinariesPath(), + getBinariesPath(), + szFileName, + CDB_PRINT_CALLSTACK_CURRENT_THREAD, + szFileNameOutput); + system(command); + + printf("\nThread Callstack:\n"); + printCallstack(szFileNameOutput, + MARKER_THREAD_CALLSTACK_START, + MARKER_THREAD_CALLSTACK_STOP); + + printf("\nException Callstack:\n"); + printCallstack(szFileNameOutput, + MARKER_EXCEPTION_CALLSTACK_START, + MARKER_EXCEPTION_CALLSTACK_STOP); + } else { + printf("Warning: CDB path not set up.\n"); + } + + return EXCEPTION_EXECUTE_HANDLER; +} + +/** Sets the debugging variables. Input parameter is app location. + * e.g out\Debug\render_pdfs.exe + * This function expects the .pdb file to be in the same directory. + */ +void setUpDebuggingFromArgs(const char* vargs0) { + int i = strlen(vargs0); + + if (i >= 4 && _stricmp(vargs0 - 4, ".exe") == 0) { + // Ignore .exe + i -= 4; + } + + int pos_period = i; + + // Find last \ in path - this is Windows! + while (i >= 0 && vargs0[i] != '\\') { + i--; + } + + int pos_last_slash = i; + + char app_name[MAX_PATH]; + strncpy(app_name, vargs0 + pos_last_slash + 1, pos_period - pos_last_slash - 1); + app_name[pos_period - pos_last_slash] = '\0'; + setAppName(app_name); + + char binaries_path[MAX_PATH]; + strncpy(binaries_path, vargs0, pos_last_slash); + binaries_path[pos_last_slash] = '\0'; + setBinariesPath(binaries_path); + + setAppVersion("1.0"); // Dummy for now, but use revision instead if we use + // the minidump for anything else other than + // collecting the callstack. + + // cdb.exe is the app used to load the minidump which prints the callstack. + char cdbExePath[MAX_PATH]; +#ifdef _WIN64 + sprintf(cdbExePath, "%s\\x64\\cdb.exe", SK_CDB_PATH); +#else + sprintf(cdbExePath, "%s\\cdb.exe", SK_CDB_PATH); +#endif + setCdbPath(cdbExePath); +} diff --git a/chromium/third_party/skia/tools/win_dbghelp.h b/chromium/third_party/skia/tools/win_dbghelp.h new file mode 100644 index 00000000000..d334318ad4b --- /dev/null +++ b/chromium/third_party/skia/tools/win_dbghelp.h @@ -0,0 +1,35 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef win_dbghelp_DEFINED +#define win_dbghelp_DEFINED + +#ifdef SK_BUILD_FOR_WIN32 + +#include <dbghelp.h> +#include <shellapi.h> +#include <shlobj.h> + +void setAppName(const char* app_name); +const char* getAppName(); + +void setBinariesPath(const char* binaries_path); +const char* getBinariesPath(); + +void setAppVersion(const char* version); +const char* getAppVersion(); + +void setCdbPath(const char* path); +const char* getCdbPath(); + +void setUpDebuggingFromArgs(const char* vargs0); + +int GenerateDumpAndPrintCallstack(EXCEPTION_POINTERS* pExceptionPointers); + +#endif // SK_BUILD_FOR_WIN32 + +#endif // win_dbghelp_DEFINED diff --git a/chromium/third_party/skia/tools/win_lcid.cpp b/chromium/third_party/skia/tools/win_lcid.cpp new file mode 100644 index 00000000000..f4901719ecf --- /dev/null +++ b/chromium/third_party/skia/tools/win_lcid.cpp @@ -0,0 +1,31 @@ +#include "windows.h" +#include "stdio.h" + +#define BUFFER_SIZE 512 +BOOL CALLBACK MyFuncLocaleEx(LPWSTR pStr, DWORD dwFlags, LPARAM lparam) { + WCHAR wcBuffer[BUFFER_SIZE]; + int bufferSize; + + bufferSize = GetLocaleInfoEx(pStr, LOCALE_SENGLANGUAGE, wcBuffer, BUFFER_SIZE); + if (bufferSize == 0) { + wprintf(L"Locale %s had error %d\n", pStr, GetLastError()); + return (TRUE); + } + + LCID lcid = LocaleNameToLCID(pStr, NULL); + if (lcid == 0) { + wprintf(L"Error %d getting LCID\n", GetLastError()); + return (TRUE); + } + + if (lcid > 0x8000) { + wprintf(L"//"); + } + wprintf(L" { 0x%.4x, \"%s\" }, //%s\n", lcid, pStr, wcBuffer); + + return (TRUE); +} + +int main(int argc, wchar_t* argv[]) { + EnumSystemLocalesEx(MyFuncLocaleEx, LOCALE_ALL, NULL, NULL); +} diff --git a/chromium/third_party/skia/tools/xsan_build b/chromium/third_party/skia/tools/xsan_build new file mode 100755 index 00000000000..f3c4d746a01 --- /dev/null +++ b/chromium/third_party/skia/tools/xsan_build @@ -0,0 +1,31 @@ +#!/bin/bash + +# Build Skia with one of Clang's many sanitizers. +# +# $ tools/xsan_build {address,thread,undefined,etc.} [any other flags to pass to make...] +# +# This script assumes the use of Clang >=3.2. +# +# For more information, see: +# http://clang.llvm.org/docs/UsersManual.html#controlling-code-generation + +set -e + +sanitizer=$1 +shift +args="$@" + +export CC="$(which clang)" +export CXX="$(which clang++)" + +if [[ -z "${CC}" ]] || [[ -z "${CXX}" ]]; then + echo "Couldn't find Clang on this machine!" + exit 1 +fi + +echo "CC=$CC" +echo "CXX=$CXX" +$CC --version + +export GYP_DEFINES="skia_sanitizer=$sanitizer ${GYP_DEFINES}" +make ${args} |