Next post: Send files between virtual machine and host

Using skia on windows

Skia is an open source 2D graphics library that serves as the graphics engine for Google Chrome, Chrome OS, and Firefox, and many others.

I recently wrote a C++ program using Skia on Windows, and thought I would write down my steps in case it helps anyone else. Building the demo applications is fairly straightforward using the documentation, but it took a bit of time to build a new application that used Skia without pulling in all of the dozens of unneeded dependencies (OpenGL, etc). I used a simple stubbing technique, writing fake implementations that will never be called just to satisfy the linker.

Prerequisites:
Visual Studio 2013 (note that the community edition is free)
7zip or WinRAR

The command prompt lines below should be run in the same session (i.e. it won't work if you close and reopen a new command prompt).

  • Download depot_tools.zip from the Install Depot Tools page
  • Use 7zip or WinRAR to Extract All to a path like c:\path\to\depot_tools (no spaces in path). (Don't use the Windows built-in zip extract.)
  • Open a command prompt
  • Run "cd c:\path\to\depot_tools"
  • Run "echo %PATH%"
  • In the output, if you already have Python installed and see a Python directory, you might want to remove this from the path. set PATH=x can do this for just this command session.
  • In the output, if you already have Git installed and see a Git directory, you might want to remove this from the path. set PATH=x can do this for just this command session.
  • Run "set PATH=%PATH%;c:\path\to\depot_tools" to add depot tools to the path
  • Run "gclient". This will download and sync the needed tools.
  • Make a directory like c:\path\to\skia (no spaces in path)
  • In the same command prompt Run "cd c:\path\to\skia"
  • Run git config --global user.name "Your Name"
  • Run git config --global user.email [email protected]
  • mkdir skia
  • cd skia
  • gclient config --name . --unmanaged https://skia.googlesource.com/skia.git
  • gclient sync
  • git checkout master
  • Run "set GYP_GENERATORS=msvs"
  • Run "python gyp_skia"
  • Run "ren out out86"
  • Run "python gyp_skia -D skia_arch_width=64"
  • Run "ren out out64"
  • Open .\out86\skia.sln in Visual Studio
  • For me, I didn't want to depend on certain unneeded projects. Open Configuration Manager, under the Debug/Release drop down, uncheck Build for the following debugger, debugger_qt_mocs, pdfviewer, pdfviewer_lib
  • Hit Build Solution, and wait several minutes
  • When the build is done, you may see some compilation warnings/errors but if the default project HelloWorld runs correctly, (Ctrl+F5), it's likely that all of the important parts work.
  • Open .\out64\skia.sln in VS
  • Repeat the above steps for x64.
Now, to create an example project that doesn't need Google's gyp system:
  • Open Visual Studio and create a new project. Other languages > Visual C++ > Win32 > Win32 Console Application
  • In the Win32 Application Wizard, click Application Settings, uncheck Precompiled Header, check Empty Project.
  • Switch to Release
  • Go into the project's options, Configuration Properties > C/C++ > General > Additional Include Diretories and add: c:\path\to\skia\include\core;c:\path\to\skia\include\config
  • Go into the project's options, Configuration Properties > C/C++ > Preprocessor > Preprocessor Definitions and add:
    WIN32
    NDEBUG
    _CONSOLE
    _LIB
    SK_INTERNAL
    SK_GAMMA_SRGB
    SK_GAMMA_APPLY_TO_A8
    SK_SCALAR_TO_FLOAT_EXCLUDED
    SK_ALLOW_STATIC_GLOBAL_INITIALIZERS=1
    SK_SUPPORT_GPU=1
    SK_SUPPORT_OPENCL=0
    SK_FORCE_DISTANCE_FIELD_TEXT=0
    SK_BUILD_FOR_WIN32
    GR_GL_FUNCTION_TYPE=__stdcall
    SK_DEVELOPER=1
    
  • Go into the project's options, Configuration Properties > Linker > Input > Additional Dependencies and add (preferably as relative paths)
    c:\path\to\skia\out86\Release\skia_core.lib
    c:\path\to\skia\out86\Release\skia_effects.lib
    c:\path\to\skia\out86\Release\skia_images.lib
    c:\path\to\skia\out86\Release\skia_opts.lib
    c:\path\to\skia\out86\Release\skia_ports.lib
    c:\path\to\skia\out86\Release\skia_sfnt.lib
    c:\path\to\skia\out86\Release\skia_utils.lib
    c:\path\to\skia\out86\Release\skia_skgpu.lib
    c:\path\to\skia\out86\Release\skia_opts_sse41.lib
    c:\path\to\skia\out86\Release\skia_opts_ssse3.lib
    c:\path\to\skia\out86\Release\lib\libetc1.lib
    c:\path\to\skia\out86\Release\lib\libSkKTX.lib 
Then, add a main.cpp to the project, with the following code,
#include <string>
#include <fstream>

#include "SkCanvas.h"
#include "SkData.h"
#include "SkDocument.h"
#include "SkGraphics.h"
#include "SkSurface.h"
#include "SkImage.h"
#include "SkStream.h"
#include "SkString.h"

#include "..\effects\SkGradientShader.h"

void save_ppm(SkBitmap const& bitmap, std::string const& filename)
{
  SkAutoLockPixels l(bitmap);

  std::ofstream ofile(filename.c_str(), std::ios_base::binary | std::ios_base::trunc);
  if (ofile.is_open())
  {
    ofile << "P6 " << bitmap.width() << " " << bitmap.height() << " 255 ";

    for (int i = 0; i != bitmap.height(); i++)
    {
      for (int j = 0; j != bitmap.width(); j++)
      {
        SkColor const* c = bitmap.getAddr32(j, i);
        char buf[3] = { SkColorGetR(*c), SkColorGetG(*c), SkColorGetB(*c) };
        ofile.write(buf, 3);
      }
    }
  }
}

void TestSkia(SkCanvas& canvas)
{
  SkPaint paint;
  paint.setAntiAlias(true);
  paint.setColor(SK_ColorRED);
  SkRect rect = {
    20, 20,
    50, 50
  };
  canvas.drawRect(rect, paint);
}

int main(int argc, char * const argv[])
{
  SkAutoGraphics ag;
  SkBitmap bitmap;
  int width = 800;
  int height = 600;
  bitmap.allocPixels(SkImageInfo::MakeN32Premul(width, height));
  SkCanvas canvas(bitmap);
  canvas.drawColor(SK_ColorWHITE);

  TestSkia(canvas);

  save_ppm(bitmap, "out.ppm");
  return 0;
}

// stub out openGl dependency, which isn't needed in this case.
extern "C"
{
#ifdef _WIN64
  PROC WINAPI __imp_wglGetProcAddress(LPCSTR)
  {
    abort();
    return nullptr;
  }
  
  HGLRC WINAPI  __imp_wglGetCurrentContext()
  {
    abort();
    return nullptr;
  }
#else
  PROC WINAPI _imp__wglGetProcAddress(LPCSTR)
  {
    abort();
    return nullptr;
  }

  HGLRC WINAPI _imp__wglGetCurrentContext()
  {
    abort();
    return nullptr;
  }
#endif
}


Running this little program will create a ppm file with a red rectangle!



To build for x64, you can create a new x64 target and update the lib directories from c:\path\to\skia\out86 to c:\path\to\skia\out64.

To add codecs for saving to different image types:
  • In Linker Inputs, add a reference to skia_codecs.lib
  • Add #include "..\images\SkForceLinking.h"
  • add the line __SK_FORCE_IMAGE_DECODER_LINKING;


To add OpenGL:
  • remove the __imp_wglGetProcAddress and __imp_wglGetCurrentContext stubs
  • In Linker Inputs, add references to the following:
    OpenGL32.lib
    usp10.lib
    kernel32.lib
    gdi32.lib
    winspool.lib
    comdlg32.lib
    advapi32.lib
    shell32.lib
    ole32.lib
    oleaut32.lib
    user32.lib
    uuid.lib
    odbc32.lib
    odbccp32.lib
    DelayImp.lib
    windowscodecs.lib