summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/skia/tools
diff options
context:
space:
mode:
authorJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-08 14:30:41 +0200
committerJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-12 13:49:54 +0200
commitab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch)
tree498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/third_party/skia/tools
parent4ce69f7403811819800e7c5ae1318b2647e778d1 (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')
-rw-r--r--chromium/third_party/skia/tools/CopyTilesRenderer.cpp101
-rw-r--r--chromium/third_party/skia/tools/CopyTilesRenderer.h50
-rw-r--r--chromium/third_party/skia/tools/CrashHandler.cpp172
-rw-r--r--chromium/third_party/skia/tools/CrashHandler.h9
-rw-r--r--chromium/third_party/skia/tools/DumpRecord.cpp113
-rw-r--r--chromium/third_party/skia/tools/DumpRecord.h24
-rw-r--r--chromium/third_party/skia/tools/LazyDecodeBitmap.cpp49
-rw-r--r--chromium/third_party/skia/tools/LazyDecodeBitmap.h24
-rw-r--r--chromium/third_party/skia/tools/OverwriteLine.h13
-rw-r--r--chromium/third_party/skia/tools/PdfRenderer.cpp60
-rw-r--r--chromium/third_party/skia/tools/PdfRenderer.h67
-rw-r--r--chromium/third_party/skia/tools/PictureBenchmark.cpp265
-rw-r--r--chromium/third_party/skia/tools/PictureBenchmark.h75
-rw-r--r--chromium/third_party/skia/tools/PictureRenderer.cpp967
-rw-r--r--chromium/third_party/skia/tools/PictureRenderer.h661
-rw-r--r--chromium/third_party/skia/tools/PictureRenderingFlags.cpp377
-rw-r--r--chromium/third_party/skia/tools/PictureRenderingFlags.h33
-rw-r--r--chromium/third_party/skia/tools/PictureResultsWriter.h232
-rw-r--r--chromium/third_party/skia/tools/Resources.cpp17
-rw-r--r--chromium/third_party/skia/tools/Resources.h15
-rw-r--r--chromium/third_party/skia/tools/Stats.h32
-rw-r--r--chromium/third_party/skia/tools/__init__.py0
-rwxr-xr-xchromium/third_party/skia/tools/add_codereview_message.py148
-rw-r--r--chromium/third_party/skia/tools/bbh_shootout.cpp194
-rw-r--r--chromium/third_party/skia/tools/bench_pictures.cfg83
-rw-r--r--chromium/third_party/skia/tools/bench_pictures_cfg_helper.py112
-rw-r--r--chromium/third_party/skia/tools/bench_pictures_main.cpp465
-rw-r--r--chromium/third_party/skia/tools/bench_playback.cpp156
-rw-r--r--chromium/third_party/skia/tools/bench_record.cpp176
-rw-r--r--chromium/third_party/skia/tools/bug_chomper/res/favicon.icobin0 -> 318 bytes
-rw-r--r--chromium/third_party/skia/tools/bug_chomper/res/style.css72
-rw-r--r--chromium/third_party/skia/tools/bug_chomper/res/third_party/jquery.tablednd.js314
-rwxr-xr-xchromium/third_party/skia/tools/bug_chomper/run_server.sh23
-rw-r--r--chromium/third_party/skia/tools/bug_chomper/src/issue_tracker/issue_tracker.go303
-rw-r--r--chromium/third_party/skia/tools/bug_chomper/src/server/server.go376
-rw-r--r--chromium/third_party/skia/tools/bug_chomper/templates/bug_chomper.html118
-rw-r--r--chromium/third_party/skia/tools/bug_chomper/templates/error.html12
-rw-r--r--chromium/third_party/skia/tools/bug_chomper/templates/submitted.html13
-rwxr-xr-xchromium/third_party/skia/tools/buildbot_globals.py76
-rw-r--r--chromium/third_party/skia/tools/chromium/chrome_changes7
-rwxr-xr-xchromium/third_party/skia/tools/compare_codereview.py414
-rw-r--r--chromium/third_party/skia/tools/copyright/fileparser.py92
-rw-r--r--chromium/third_party/skia/tools/copyright/main.py107
-rwxr-xr-xchromium/third_party/skia/tools/coverage.sh31
-rw-r--r--chromium/third_party/skia/tools/doxygen_footer.txt9
-rw-r--r--chromium/third_party/skia/tools/dump_record.cpp80
-rw-r--r--chromium/third_party/skia/tools/filtermain.cpp847
-rwxr-xr-xchromium/third_party/skia/tools/find_bad_images_in_skps.py197
-rw-r--r--chromium/third_party/skia/tools/find_run_binary.py61
-rw-r--r--chromium/third_party/skia/tools/flags/SkCommandLineFlags.cpp356
-rw-r--r--chromium/third_party/skia/tools/flags/SkCommandLineFlags.h446
-rwxr-xr-xchromium/third_party/skia/tools/gcov_shim15
-rw-r--r--chromium/third_party/skia/tools/gen_bench_expectations_from_codereview.py183
-rw-r--r--chromium/third_party/skia/tools/generate_fir_coeff.py119
-rw-r--r--chromium/third_party/skia/tools/git-skia-verify95
-rwxr-xr-xchromium/third_party/skia/tools/git-sync-deps205
-rw-r--r--chromium/third_party/skia/tools/git_utils.py168
-rw-r--r--chromium/third_party/skia/tools/gpuveto.cpp78
-rw-r--r--chromium/third_party/skia/tools/image_expectations.cpp232
-rw-r--r--chromium/third_party/skia/tools/image_expectations.h149
-rwxr-xr-xchromium/third_party/skia/tools/install_dependencies.sh36
-rwxr-xr-xchromium/third_party/skia/tools/jsondiff.py201
-rw-r--r--chromium/third_party/skia/tools/lsan.supp16
-rwxr-xr-xchromium/third_party/skia/tools/lua/agg_dash.lua29
-rw-r--r--chromium/third_party/skia/tools/lua/bbh_filter.lua148
-rw-r--r--chromium/third_party/skia/tools/lua/bitmap_statistics.lua59
-rw-r--r--chromium/third_party/skia/tools/lua/chars-vs-glyphs.lua35
-rw-r--r--chromium/third_party/skia/tools/lua/classify_rrect_clips.lua109
-rw-r--r--chromium/third_party/skia/tools/lua/count_effects.lua45
-rw-r--r--chromium/third_party/skia/tools/lua/count_reduced_clipstacks.lua87
-rw-r--r--chromium/third_party/skia/tools/lua/dump_clipstack_at_restore.lua33
-rw-r--r--chromium/third_party/skia/tools/lua/dumpops.lua34
-rw-r--r--chromium/third_party/skia/tools/lua/glyph-usage.lua157
-rw-r--r--chromium/third_party/skia/tools/lua/gradients.lua34
-rw-r--r--chromium/third_party/skia/tools/lua/lua_app.cpp63
-rw-r--r--chromium/third_party/skia/tools/lua/lua_pictures.cpp176
-rw-r--r--chromium/third_party/skia/tools/lua/scrape.lua80
-rw-r--r--chromium/third_party/skia/tools/lua/scrape_dashing.lua93
-rwxr-xr-xchromium/third_party/skia/tools/lua/scrape_dashing_full.lua146
-rw-r--r--chromium/third_party/skia/tools/lua/skia.lua91
-rwxr-xr-xchromium/third_party/skia/tools/merge_static_libs.py66
-rw-r--r--chromium/third_party/skia/tools/misc_utils.py224
-rw-r--r--chromium/third_party/skia/tools/pathops_sorter.htm2202
-rw-r--r--chromium/third_party/skia/tools/pathops_visualizer.htm3330
-rw-r--r--chromium/third_party/skia/tools/picture_utils.cpp72
-rw-r--r--chromium/third_party/skia/tools/picture_utils.h56
-rw-r--r--chromium/third_party/skia/tools/pinspect.cpp89
-rw-r--r--chromium/third_party/skia/tools/pyutils/__init__.py0
-rwxr-xr-xchromium/third_party/skia/tools/pyutils/gs_utils.py81
-rwxr-xr-xchromium/third_party/skia/tools/pyutils/url_utils.py63
-rwxr-xr-xchromium/third_party/skia/tools/pyutils/url_utils_test.py61
-rwxr-xr-xchromium/third_party/skia/tools/reformat-json.py55
-rw-r--r--chromium/third_party/skia/tools/render_pdfs_main.cpp302
-rw-r--r--chromium/third_party/skia/tools/render_pictures_main.cpp503
-rw-r--r--chromium/third_party/skia/tools/retrieve_from_googlesource.py37
-rwxr-xr-xchromium/third_party/skia/tools/roll_deps.py543
-rwxr-xr-xchromium/third_party/skia/tools/sanitize_source_files.py152
-rw-r--r--chromium/third_party/skia/tools/sk_tool_utils.cpp32
-rw-r--r--chromium/third_party/skia/tools/sk_tool_utils.h25
-rw-r--r--chromium/third_party/skia/tools/skdiff.cpp228
-rw-r--r--chromium/third_party/skia/tools/skdiff.h272
-rw-r--r--chromium/third_party/skia/tools/skdiff_html.cpp313
-rw-r--r--chromium/third_party/skia/tools/skdiff_html.h21
-rw-r--r--chromium/third_party/skia/tools/skdiff_image.cpp377
-rw-r--r--chromium/third_party/skia/tools/skdiff_main.cpp803
-rw-r--r--chromium/third_party/skia/tools/skdiff_utils.cpp184
-rw-r--r--chromium/third_party/skia/tools/skdiff_utils.h53
-rw-r--r--chromium/third_party/skia/tools/skhello.cpp110
-rw-r--r--chromium/third_party/skia/tools/skimage_main.cpp842
-rw-r--r--chromium/third_party/skia/tools/skpdiff/README12
-rw-r--r--chromium/third_party/skia/tools/skpdiff/SkCLImageDiffer.cpp122
-rw-r--r--chromium/third_party/skia/tools/skpdiff/SkCLImageDiffer.h97
-rw-r--r--chromium/third_party/skia/tools/skpdiff/SkDiffContext.cpp360
-rw-r--r--chromium/third_party/skia/tools/skpdiff/SkDiffContext.h143
-rw-r--r--chromium/third_party/skia/tools/skpdiff/SkDifferentPixelsMetric.h49
-rw-r--r--chromium/third_party/skia/tools/skpdiff/SkDifferentPixelsMetric_cpu.cpp67
-rw-r--r--chromium/third_party/skia/tools/skpdiff/SkDifferentPixelsMetric_opencl.cpp120
-rw-r--r--chromium/third_party/skia/tools/skpdiff/SkImageDiffer.cpp19
-rw-r--r--chromium/third_party/skia/tools/skpdiff/SkImageDiffer.h53
-rw-r--r--chromium/third_party/skia/tools/skpdiff/SkPMetric.cpp466
-rw-r--r--chromium/third_party/skia/tools/skpdiff/SkPMetric.h30
-rw-r--r--chromium/third_party/skia/tools/skpdiff/SkPMetricUtil_generated.h2586
-rw-r--r--chromium/third_party/skia/tools/skpdiff/diff_viewer.js306
-rwxr-xr-xchromium/third_party/skia/tools/skpdiff/generate_pmetric_tables.py149
-rw-r--r--chromium/third_party/skia/tools/skpdiff/skpdiff_main.cpp236
-rwxr-xr-xchromium/third_party/skia/tools/skpdiff/skpdiff_server.py580
-rw-r--r--chromium/third_party/skia/tools/skpdiff/skpdiff_util.cpp206
-rw-r--r--chromium/third_party/skia/tools/skpdiff/skpdiff_util.h60
-rw-r--r--chromium/third_party/skia/tools/skpdiff/viewer.html81
-rw-r--r--chromium/third_party/skia/tools/skpdiff/viewer_style.css173
-rw-r--r--chromium/third_party/skia/tools/skpinfo.cpp162
-rw-r--r--chromium/third_party/skia/tools/skpmaker.cpp85
-rwxr-xr-xchromium/third_party/skia/tools/submit_try294
-rw-r--r--chromium/third_party/skia/tools/submit_try.bat1
-rw-r--r--chromium/third_party/skia/tools/svn.py195
-rwxr-xr-xchromium/third_party/skia/tools/svndiff.py336
-rwxr-xr-xchromium/third_party/skia/tools/test_all.py32
-rwxr-xr-xchromium/third_party/skia/tools/test_gpuveto.py168
-rw-r--r--chromium/third_party/skia/tools/test_image_decoder.cpp39
-rw-r--r--chromium/third_party/skia/tools/test_pdfs.py60
-rw-r--r--chromium/third_party/skia/tools/tsan.supp27
-rw-r--r--chromium/third_party/skia/tools/valgrind.supp202
-rw-r--r--chromium/third_party/skia/tools/win_dbghelp.cpp243
-rw-r--r--chromium/third_party/skia/tools/win_dbghelp.h35
-rw-r--r--chromium/third_party/skia/tools/win_lcid.cpp31
-rwxr-xr-xchromium/third_party/skia/tools/xsan_build31
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(&copy, &subset);
+ } else {
+#endif
+ dst.copyTo(&copy);
+#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
new file mode 100644
index 00000000000..e7440c75127
--- /dev/null
+++ b/chromium/third_party/skia/tools/bug_chomper/res/favicon.ico
Binary files differ
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(&copy);
+ 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 + '&nbsp;';\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(&copy, 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(&copy, 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(&currentTime);
+ QueryPerformanceFrequency(&frequency);
+ return (double)currentTime.QuadPart / (double)frequency.QuadPart;
+#elif _POSIX_TIMERS > 0 && defined(CLOCK_REALTIME)
+ struct timespec currentTime;
+ clock_gettime(CLOCK_REALTIME, &currentTime);
+ 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(&currentTime, 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;">&nbsp;</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}