/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // TODO: Fix Skia. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #include #include #include #pragma GCC diagnostic pop using namespace android; #define COLORSPACE_UNKNOWN 0 #define COLORSPACE_SRGB 1 #define COLORSPACE_DISPLAY_P3 2 static void usage(const char* pname, PhysicalDisplayId displayId) { fprintf(stderr, "usage: %s [-hp] [-d display-id] [FILENAME]\n" " -h: this message\n" " -p: save the file as a png.\n" " -d: specify the physical display ID to capture (default: %" ANDROID_PHYSICAL_DISPLAY_ID_FORMAT ")\n" " see \"dumpsys SurfaceFlinger --display-id\" for valid display IDs.\n" "If FILENAME ends with .png it will be saved as a png.\n" "If FILENAME is not given, the results will be printed to stdout.\n", pname, displayId); } static SkColorType flinger2skia(PixelFormat f) { switch (f) { case PIXEL_FORMAT_RGB_565: return kRGB_565_SkColorType; default: return kN32_SkColorType; } } static sk_sp dataSpaceToColorSpace(ui::Dataspace d) { switch (d) { case ui::Dataspace::V0_SRGB: return SkColorSpace::MakeSRGB(); case ui::Dataspace::DISPLAY_P3: return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3); default: return nullptr; } } static ui::Dataspace pickBestDataspace(ui::ColorMode colorMode) { switch (colorMode) { case ui::ColorMode::SRGB: return ui::Dataspace::V0_SRGB; case ui::ColorMode::DISPLAY_P3: case ui::ColorMode::BT2100_PQ: case ui::ColorMode::BT2100_HLG: return ui::Dataspace::DISPLAY_P3; default: return ui::Dataspace::V0_SRGB; } } static uint32_t dataSpaceToInt(ui::Dataspace d) { switch (d) { case ui::Dataspace::V0_SRGB: return COLORSPACE_SRGB; case ui::Dataspace::DISPLAY_P3: return COLORSPACE_DISPLAY_P3; default: return COLORSPACE_UNKNOWN; } } static status_t notifyMediaScanner(const char* fileName) { String8 cmd("am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://"); cmd.append(fileName); cmd.append(" > /dev/null"); int result = system(cmd.string()); if (result < 0) { fprintf(stderr, "Unable to broadcast intent for media scanner.\n"); return UNKNOWN_ERROR; } return NO_ERROR; } int main(int argc, char** argv) { std::optional displayId = SurfaceComposerClient::getInternalDisplayId(); if (!displayId) { fprintf(stderr, "Failed to get token for internal display\n"); return 1; } const char* pname = argv[0]; bool png = false; int c; while ((c = getopt(argc, argv, "phd:")) != -1) { switch (c) { case 'p': png = true; break; case 'd': displayId = atoll(optarg); break; case '?': case 'h': usage(pname, *displayId); return 1; } } argc -= optind; argv += optind; int fd = -1; const char* fn = NULL; if (argc == 0) { fd = dup(STDOUT_FILENO); } else if (argc == 1) { fn = argv[0]; fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664); if (fd == -1) { fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno)); return 1; } const int len = strlen(fn); if (len >= 4 && 0 == strcmp(fn+len-4, ".png")) { png = true; } } if (fd == -1) { usage(pname, *displayId); return 1; } void const* mapbase = MAP_FAILED; ssize_t mapsize = -1; void* base = NULL; uint32_t w, s, h, f; size_t size = 0; // Maps orientations from DisplayInfo to ISurfaceComposer static const uint32_t ORIENTATION_MAP[] = { ISurfaceComposer::eRotateNone, // 0 == DISPLAY_ORIENTATION_0 ISurfaceComposer::eRotate270, // 1 == DISPLAY_ORIENTATION_90 ISurfaceComposer::eRotate180, // 2 == DISPLAY_ORIENTATION_180 ISurfaceComposer::eRotate90, // 3 == DISPLAY_ORIENTATION_270 }; // setThreadPoolMaxThreadCount(0) actually tells the kernel it's // not allowed to spawn any additional threads, but we still spawn // a binder thread from userspace when we call startThreadPool(). // See b/36066697 for rationale ProcessState::self()->setThreadPoolMaxThreadCount(0); ProcessState::self()->startThreadPool(); const sp display = SurfaceComposerClient::getPhysicalDisplayToken(*displayId); if (display == nullptr) { fprintf(stderr, "Failed to get token for invalid display %" ANDROID_PHYSICAL_DISPLAY_ID_FORMAT "\n", *displayId); return 1; } Vector configs; SurfaceComposerClient::getDisplayConfigs(display, &configs); int activeConfig = SurfaceComposerClient::getActiveConfig(display); if (static_cast(activeConfig) >= configs.size()) { fprintf(stderr, "Active config %d not inside configs (size %zu)\n", activeConfig, configs.size()); return 1; } uint8_t displayOrientation = configs[activeConfig].orientation; uint32_t captureOrientation = ORIENTATION_MAP[displayOrientation]; sp outBuffer; ui::Dataspace reqDataspace = pickBestDataspace(SurfaceComposerClient::getActiveColorMode(display)); // Due to the fact that we hard code the way we write pixels into screenshot, // we hard code RGBA_8888 here. ui::PixelFormat reqPixelFormat = ui::PixelFormat::RGBA_8888; status_t result = ScreenshotClient::capture(display, reqDataspace, reqPixelFormat, Rect(), 0 /* reqWidth */, 0 /* reqHeight */, false, captureOrientation, &outBuffer); if (result != NO_ERROR) { close(fd); return 1; } result = outBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base); if (base == nullptr || result != NO_ERROR) { String8 reason; if (base == nullptr) { reason = "Failed to write to buffer"; } else { reason.appendFormat("Error Code: %d", result); } fprintf(stderr, "Failed to take screenshot (%s)\n", reason.c_str()); close(fd); return 1; } w = outBuffer->getWidth(); h = outBuffer->getHeight(); s = outBuffer->getStride(); f = outBuffer->getPixelFormat(); size = s * h * bytesPerPixel(f); if (png) { const SkImageInfo info = SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType, dataSpaceToColorSpace(reqDataspace)); SkPixmap pixmap(info, base, s * bytesPerPixel(f)); struct FDWStream final : public SkWStream { size_t fBytesWritten = 0; int fFd; FDWStream(int f) : fFd(f) {} size_t bytesWritten() const override { return fBytesWritten; } bool write(const void* buffer, size_t size) override { fBytesWritten += size; return size == 0 || ::write(fFd, buffer, size) > 0; } } fdStream(fd); (void)SkEncodeImage(&fdStream, pixmap, SkEncodedImageFormat::kPNG, 100); if (fn != NULL) { notifyMediaScanner(fn); } } else { uint32_t c = dataSpaceToInt(reqDataspace); write(fd, &w, 4); write(fd, &h, 4); write(fd, &f, 4); write(fd, &c, 4); size_t Bpp = bytesPerPixel(f); for (size_t y=0 ; y