skia 将网页录制成 skp 文件 通过skia的dm或者其他方式重放
这个文档在:https://skia.org/docs/user/tips/
dm是个重要的工具,实现将skp转成图片或者pdf。源码在 third_party\skia\dm\DM.cpp
命令行生成 screenshot 是屏幕截屏方式:
google-chrome --headless --screenshot=/path/to/screenshot.png https://example.com
1,Use the script experimental/tools/web_to_skp
, or do the following:
- Launch Chrome or Chromium with
--no-sandbox --enable-gpu-benchmarking
- Open the JS console (Ctrl+Shift+J (Windows / Linux) or Cmd+Opt+J (MacOS))
- Execute:
chrome.gpuBenchmarking.printToSkPicture('/tmp')
This returns “undefined” on success.
或者 chrome.gpuBenchmarking.printPagesToSkPictures ('/tmp/filename.mskp') 输出 mskp 文件。
/tmp是个存放的目录。windows可以写成 chrome.gpuBenchmarking.printToSkPicture('d:\\mySkp')
Open the resulting file in the Skia Debugger, rasterize it with dm
, or use Skia’s viewer
to view it:
out/Release/dm --src skp --skps /tmp/layer_0.skp -w /tmp \
--config 8888 gpu pdf --verbose
ls -l /tmp/*/skp/layer_0.skp.*
out/Release/viewer --skps /tmp --slide layer_0.skp
1.1 在源码中的实现:
#include "base/base64.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/debug/profiler.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
void GpuBenchmarking::PrintToSkPicture(v8::Isolate* isolate, const std::string& dirname) { GpuBenchmarkingContext context(render_frame_.get()); const cc::Layer* root_layer = context.layer_tree_host()->root_layer(); if (!root_layer) return; base::FilePath dirpath = base::FilePath::FromUTF8Unsafe(dirname); if (!base::CreateDirectory(dirpath) || !base::PathIsWritable(dirpath)) { std::string msg("Path is not writable: "); msg.append(dirpath.MaybeAsASCII()); isolate->ThrowException(v8::Exception::Error( v8::String::NewFromUtf8(isolate, msg.c_str(), v8::NewStringType::kNormal, msg.length()) .ToLocalChecked())); return; } SkPictureSerializer serializer(dirpath); serializer.Serialize(root_layer); }
serializer:
void Serialize(const cc::Layer* root_layer) { for (auto* layer : *root_layer->layer_tree_host()) { sk_sp<SkPicture> picture = layer->GetPicture(); if (!picture) continue; // Serialize picture to file. // TODO(alokp): Note that for this to work Chrome needs to be launched // with // --no-sandbox command-line flag. Get rid of this limitation. // CRBUG: 139640. std::string filename = "layer_" + base::NumberToString(layer_id_++) + ".skp"; std::string filepath = dirpath_.AppendASCII(filename).MaybeAsASCII(); DCHECK(!filepath.empty()); SkFILEWStream file(filepath.c_str()); DCHECK(file.isValid()); auto data = picture->serialize(); file.write(data->data(), data->size()); file.fsync(); } }
GetPicture:
sk_sp<SkPicture> PictureLayer::GetPicture() const { if (!DrawsContent() || bounds().IsEmpty()) return nullptr; scoped_refptr<DisplayItemList> display_list = picture_layer_inputs_.client->PaintContentsToDisplayList(); SkPictureRecorder recorder; SkCanvas* canvas = recorder.beginRecording(bounds().width(), bounds().height()); canvas->clear(SK_ColorTRANSPARENT); display_list->Raster(canvas); return recorder.finishRecordingAsPicture(); }
1.2 draw paint录制:
void SkCanvas::drawPaint(const SkPaint& paint) { TRACE_EVENT0("skia", TRACE_FUNC); this->onDrawPaint(paint); }
重放,参考 canvaskit/node_build/extra.html
canvaskit draw skp
function SkpExample(CanvasKit, skpData) { if (!skpData || !CanvasKit) { return; } const surface = CanvasKit.MakeSWCanvasSurface('skp'); if (!surface) { console.error('Could not make surface'); return; } const pic = CanvasKit.MakePicture(skpData); function drawFrame(canvas) { canvas.clear(CanvasKit.TRANSPARENT); // this particular file has a path drawing at (68,582) that's 1300x1300 pixels // scale it down to 500x500 and translate it to fit. const scale = 500.0/1300; canvas.scale(scale, scale); canvas.translate(-68, -582); canvas.drawPicture(pic); } // Intentionally just draw frame once surface.drawOnce(drawFrame); }
2, 也可录制多个页面,还原成pdf
为什么要抓取多个skp,是因为一个页面是分层的,分成layer。这些layer合起来才是最终显示的页面效果。比如百度首页,就是三个layer合成的。
Capture a .mskp
file on a web page in Chromium
Multipage Skia Picture files capture the commands sent to produce PDFs and printed documents.
Use the script experimental/tools/web_to_mskp
, or do the following:
- Launch Chrome or Chromium with
--no-sandbox --enable-gpu-benchmarking
- Open the JS console (Ctrl+Shift+J (Windows / Linux) or Cmd+Opt+J (MacOS))
- Execute:
chrome.gpuBenchmarking.printPagesToSkPictures('/tmp/filename.mskp')
This returns “undefined” on success.
Open the resulting file in the Skia Debugger or process it with dm
.
experimental/tools/mskp_parser.py /tmp/filename.mskp /tmp/filename.mskp.skp
ls -l /tmp/filename.mskp.skp
# open filename.mskp.skp in the debugger.
out/Release/dm --src mskp --mskps /tmp/filename.mskp -w /tmp \
--config pdf --verbose
ls -l /tmp/pdf/mskp/filename.mskp.pdf
#生成图片:
out/Release/dm --src mskp --mskps /tmp/filename.mskp -w /tmp \
--config png --verbose
方法3
1. **截取 SKP 文件**:
- 在 Chrome 中,您可以使用命令行标志来启用 Skia 的记录功能,将渲染命令记录到 SKP 文件中。这样可以在不显示页面的情况下捕获渲染命令。
- 启动 Chrome 时使用如下标志:
```bash
chrome --enable-skia-benchmarking --skia-benchmarking-record-file=output.skp
```
2. **生成图片**:
- 使用 Skia 的工具 `skia_tool`(Skia 的开发工具之一)来将 SKP 文件转换为图片。
```bash
skia_tool dump -o output.png output.skp
```
How to add hardware acceleration in Skia
There are two ways Skia takes advantage of specific hardware.
-
Custom bottleneck routines
There are sets of bottleneck routines inside the blits of Skia that can be replace on a platform in order to take advantage of specific CPU features. One such example is the NEON SIMD instructions on ARM v7 devices. See src/opts/
Does Skia support Font hinting?
Skia has a built-in font cache, but it does not know how to actually render font files like TrueType into its cache. For that it relies on the platform to supply an instance of SkScalerContext
. This is Skia’s abstract interface for communicating with a font scaler engine. In src/ports you can see support files for FreeType, macOS, and Windows GDI font engines. Other font engines can easily be supported in a like manner.
Does Skia shape text (kerning)?
Shaping is the process that translates a span of Unicode text into a span of positioned glyphs with the appropriate typefaces.
Skia does not shape text. Skia provides interfaces to draw glyphs, but does not implement a text shaper. Skia’s client’s often use HarfBuzz to generate the glyphs and their positions, including kerning.
Here is an example of how to use Skia and HarfBuzz together. In the example, a SkTypeface
and a hb_face_t
are created using the same mmap()
ed .ttf
font file. The HarfBuzz face is used to shape unicode text into a sequence of glyphs and positions, and the SkTypeface
can then be used to draw those glyphs.
How do I add drop shadow on text?
void draw(SkCanvas* canvas) {
const SkScalar sigma = 1.65f;
const SkScalar xDrop = 2.0f;
const SkScalar yDrop = 2.0f;
const SkScalar x = 8.0f;
const SkScalar y = 52.0f;
const SkScalar textSize = 48.0f;
const uint8_t blurAlpha = 127;
auto blob = SkTextBlob::MakeFromString("Skia", SkFont(nullptr, textSize));
SkPaint paint;
paint.setAntiAlias(true);
SkPaint blur(paint);
blur.setAlpha(blurAlpha);
blur.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, sigma, 0));
canvas->drawColor(SK_ColorWHITE);
canvas->drawTextBlob(blob.get(), x + xDrop, y + yDrop, blur);
canvas->drawTextBlob(blob.get(), x, y, paint);
}
content\renderer\gpu_benchmarking_extension.cc
这个文件有参考价值
- 关于冻结页面:
void GpuBenchmarking::Freeze() {
GpuBenchmarkingContext context(render_frame_.get());
// TODO(fmeawad): Instead of forcing a visibility change, only allow
// freezing a page if it was already hidden.
context.web_view()->SetVisibilityState(
blink::mojom::PageVisibilityState::kHidden,
/*is_initial_state=*/false);
context.web_view()->SetPageFrozen(true);
}
-
打印页面成不是pdf而是 mskp
static void PrintDocumentTofile(v8::Isolate* isolate,
const std::string& filename,
sk_sp<SkDocument> (*make_doc)(SkWStream*),
RenderFrameImpl* render_frame) {
GpuBenchmarkingContext context(render_frame);
base::FilePath path = base::FilePath::FromUTF8Unsafe(filename);
if (!base::PathIsWritable(path.DirName())) {
std::string msg("Path is not writable: ");
msg.append(path.DirName().MaybeAsASCII());
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, msg.c_str(),
v8::NewStringType::kNormal, msg.length())
.ToLocalChecked()));
return;
}
SkFILEWStream wStream(path.MaybeAsASCII().c_str());
sk_sp<SkDocument> doc = make_doc(&wStream);
if (doc) {
context.web_frame()->View()->GetSettings()->SetShouldPrintBackgrounds(true);
PrintDocument(context.web_frame(), doc.get());
doc->close();
}
}
static void PrintDocument(blink::WebLocalFrame* frame, SkDocument* doc) {
const float kPageWidth = 612.0f; // 8.5 inch
const float kPageHeight = 792.0f; // 11 inch
const float kMarginTop = 29.0f; // 0.40 inch
const float kMarginLeft = 29.0f; // 0.40 inch
const int kContentWidth = 555; // 7.71 inch
const int kContentHeight = 735; // 10.21 inch
blink::WebPrintParams params(gfx::Size(kContentWidth, kContentHeight));
params.printer_dpi = 300;
uint32_t page_count = frame->PrintBegin(params, blink::WebNode());
for (uint32_t i = 0; i < page_count; ++i) {
SkCanvas* sk_canvas = doc->beginPage(kPageWidth, kPageHeight);
cc::SkiaPaintCanvas canvas(sk_canvas);
cc::PaintCanvasAutoRestore auto_restore(&canvas, true);
canvas.translate(kMarginLeft, kMarginTop);
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
float page_shrink = frame->GetPrintPageShrink(i);
DCHECK_GT(page_shrink, 0);
canvas.scale(page_shrink, page_shrink);
#endif
frame->PrintPage(i, &canvas);
}
frame->PrintEnd();
}
make_doc函数指针是
sk_sp<SkDocument> make_multipicturedocument(SkWStream* stream) {
return SkMakeMultiPictureDocument(stream);
}
还可以改成打印成微软的xps文件 MakeXPSDocument
skp输出成图片png
Vector<uint8_t> PictureSnapshot::Replay(unsigned from_step,
unsigned to_step,
double scale) const {
const SkIRect bounds = picture_->cullRect().roundOut();
int width = ceil(scale * bounds.width());
int height = ceil(scale * bounds.height());
// TODO(fmalita): convert this to SkSurface/SkImage, drop the intermediate
// SkBitmap.
SkBitmap bitmap;
bitmap.allocPixels(SkImageInfo::MakeN32Premul(width, height));
bitmap.eraseARGB(0, 0, 0, 0);
{
ReplayingCanvas canvas(bitmap, from_step, to_step);
// Disable LCD text preemptively, because the picture opacity is unknown.
// The canonical API involves SkSurface props, but since we're not
// SkSurface-based at this point (see TODO above) we (ab)use saveLayer for
// this purpose.
SkAutoCanvasRestore auto_restore(&canvas, false);
canvas.saveLayer(nullptr, nullptr);
canvas.scale(scale, scale);
canvas.ResetStepCount();
picture_->playback(&canvas, &canvas);
}
Vector<uint8_t> encoded_image;
SkPixmap src;
bool peekResult = bitmap.peekPixels(&src);
DCHECK(peekResult);
SkPngEncoder::Options options;
options.fFilterFlags = SkPngEncoder::FilterFlag::kSub;
options.fZLibLevel = 3;
if (!ImageEncoder::Encode(&encoded_image, src, options))
return Vector<uint8_t>();
return encoded_image;
}
一个可以js加载扩展接口
void GpuBenchmarking::Install(base::WeakPtr<RenderFrameImpl> frame) {
v8::Isolate* isolate = blink::MainThreadIsolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context =
frame->GetWebFrame()->MainWorldScriptContext();
if (context.IsEmpty())
return;
v8::Context::Scope context_scope(context);
gin::Handle<GpuBenchmarking> controller =
gin::CreateHandle(isolate, new GpuBenchmarking(frame));
if (controller.IsEmpty())
return;
v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
chrome
->Set(context, gin::StringToV8(isolate, "gpuBenchmarking"),
controller.ToV8())
.Check();
}
gin::ObjectTemplateBuilder GpuBenchmarking::GetObjectTemplateBuilder(
v8::Isolate* isolate) {
return gin::Wrappable<GpuBenchmarking>::GetObjectTemplateBuilder(isolate)
.SetMethod("setNeedsDisplayOnAllLayers",
&GpuBenchmarking::SetNeedsDisplayOnAllLayers)
.SetMethod("setRasterizeOnlyVisibleContent",
&GpuBenchmarking::SetRasterizeOnlyVisibleContent)
.SetMethod("printToSkPicture", &GpuBenchmarking::PrintToSkPicture)
.SetMethod("printPagesToSkPictures",
&GpuBenchmarking::PrintPagesToSkPictures)
.SetMethod("printPagesToXPS", &GpuBenchmarking::PrintPagesToXPS)
.SetValue("DEFAULT_INPUT",
GestureSourceTypeAsInt(
content::mojom::GestureSourceType::kDefaultInput))
.SetValue("TOUCH_INPUT",
GestureSourceTypeAsInt(
content::mojom::GestureSourceType::kTouchInput))
.SetValue("MOUSE_INPUT",
GestureSourceTypeAsInt(
content::mojom::GestureSourceType::kMouseInput))
.SetValue("TOUCHPAD_INPUT",
GestureSourceTypeAsInt(
content::mojom::GestureSourceType::kTouchpadInput))
.SetMethod("runMicroBenchmark", &GpuBenchmarking::RunMicroBenchmark)。。。。。;