bool SkCreateBitmapFromCGImage(SkBitmap* dst, CGImageRef image, SkISize* scaleToFit) { const int width = scaleToFit ? scaleToFit->width() : SkToInt(CGImageGetWidth(image)); const int height = scaleToFit ? scaleToFit->height() : SkToInt(CGImageGetHeight(image)); SkImageInfo info = SkImageInfo::MakeN32Premul(width, height); SkBitmap tmp; if (!tmp.allocPixels(info)) { return false; } if (!SkCopyPixelsFromCGImage(tmp.info(), tmp.rowBytes(), tmp.getPixels(), image)) { return false; } CGImageAlphaInfo cgInfo = CGImageGetAlphaInfo(image); switch (cgInfo) { case kCGImageAlphaNone: case kCGImageAlphaNoneSkipLast: case kCGImageAlphaNoneSkipFirst: SkASSERT(SkBitmap::ComputeIsOpaque(tmp)); tmp.setAlphaType(kOpaque_SkAlphaType); break; default: // we don't know if we're opaque or not, so compute it. if (SkBitmap::ComputeIsOpaque(tmp)) { tmp.setAlphaType(kOpaque_SkAlphaType); } } *dst = tmp; return true; }
bool SkImageDecoder_CG::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { CGImageSourceRef imageSrc = SkStreamToCGImageSource(stream); if (NULL == imageSrc) { return false; } SkAutoTCallVProc<const void, CFRelease> arsrc(imageSrc); CGImageRef image = CGImageSourceCreateImageAtIndex(imageSrc, 0, NULL); if (NULL == image) { return false; } SkAutoTCallVProc<CGImage, CGImageRelease> arimage(image); const int width = SkToInt(CGImageGetWidth(image)); const int height = SkToInt(CGImageGetHeight(image)); bm->setInfo(SkImageInfo::MakeN32Premul(width, height)); if (SkImageDecoder::kDecodeBounds_Mode == mode) { return true; } if (!this->allocPixelRef(bm, NULL)) { return false; } SkAutoLockPixels alp(*bm); if (!SkCopyPixelsFromCGImage(bm->info(), bm->rowBytes(), bm->getPixels(), image)) { return false; } CGImageAlphaInfo info = CGImageGetAlphaInfo(image); switch (info) { case kCGImageAlphaNone: case kCGImageAlphaNoneSkipLast: case kCGImageAlphaNoneSkipFirst: SkASSERT(SkBitmap::ComputeIsOpaque(*bm)); bm->setAlphaType(kOpaque_SkAlphaType); break; default: // we don't know if we're opaque or not, so compute it. if (SkBitmap::ComputeIsOpaque(*bm)) { bm->setAlphaType(kOpaque_SkAlphaType); } } if (!bm->isOpaque() && this->getRequireUnpremultipliedColors()) { // CGBitmapContext does not support unpremultiplied, so the image has been premultiplied. // Convert to unpremultiplied. for (int i = 0; i < width; ++i) { for (int j = 0; j < height; ++j) { uint32_t* addr = bm->getAddr32(i, j); *addr = SkUnPreMultiply::UnPreMultiplyPreservingByteOrder(*addr); } } bm->setAlphaType(kUnpremul_SkAlphaType); } return true; }
void GrResourceCache::dumpStats(SkString* out) const { this->validate(); int locked = fNonpurgeableResources.count(); struct Stats { int fScratch; int fExternal; int fBorrowed; int fAdopted; size_t fUnbudgetedSize; Stats() : fScratch(0), fExternal(0), fBorrowed(0), fAdopted(0), fUnbudgetedSize(0) {} void update(GrGpuResource* resource) { if (resource->cacheAccess().isScratch()) { ++fScratch; } if (resource->cacheAccess().isExternal()) { ++fExternal; } if (resource->cacheAccess().isBorrowed()) { ++fBorrowed; } if (resource->cacheAccess().isAdopted()) { ++fAdopted; } if (!resource->resourcePriv().isBudgeted()) { fUnbudgetedSize += resource->gpuMemorySize(); } } }; Stats stats; for (int i = 0; i < fNonpurgeableResources.count(); ++i) { stats.update(fNonpurgeableResources[i]); } for (int i = 0; i < fPurgeableQueue.count(); ++i) { stats.update(fPurgeableQueue.at(i)); } float countUtilization = (100.f * fBudgetedCount) / fMaxCount; float byteUtilization = (100.f * fBudgetedBytes) / fMaxBytes; out->appendf("Budget: %d items %d bytes\n", fMaxCount, (int)fMaxBytes); out->appendf("\t\tEntry Count: current %d" " (%d budgeted, %d external(%d borrowed, %d adopted), %d locked, %d scratch %.2g%% full), high %d\n", this->getResourceCount(), fBudgetedCount, stats.fExternal, stats.fBorrowed, stats.fAdopted, locked, stats.fScratch, countUtilization, fHighWaterCount); out->appendf("\t\tEntry Bytes: current %d (budgeted %d, %.2g%% full, %d unbudgeted) high %d\n", SkToInt(fBytes), SkToInt(fBudgetedBytes), byteUtilization, SkToInt(stats.fUnbudgetedSize), SkToInt(fHighWaterBytes)); }
static void excercise_draw_pos_text(SkCanvas* canvas, const char* text, SkScalar x, SkScalar y, const SkPaint& paint) { size_t textLen = strlen(text); SkAutoTArray<SkScalar> widths(SkToInt(textLen)); paint.getTextWidths(text, textLen, &widths[0]); SkAutoTArray<SkPoint> pos(SkToInt(textLen)); for (int i = 0; i < SkToInt(textLen); ++i) { pos[i].set(x, y); x += widths[i]; } canvas->drawPosText(text, textLen, &pos[0], paint); }
void drawTestCase(SkCanvas* canvas, const char* text, SkScalar y, const SkPaint& paint) { SkScalar widths[kMaxStringLength]; SkScalar posX[kMaxStringLength]; SkPoint pos[kMaxStringLength]; int length = SkToInt(strlen(text)); SkASSERT(length <= kMaxStringLength); paint.getTextWidths(text, length, widths); float originX; switch (paint.getTextAlign()) { case SkPaint::kRight_Align: originX = 1; break; case SkPaint::kCenter_Align: originX = 0.5f; break; case SkPaint::kLeft_Align: originX = 0; break; default: SkFAIL("Invalid paint origin"); return; } float x = kTextHeight; for (int i = 0; i < length; ++i) { posX[i] = x + originX * widths[i]; pos[i].set(posX[i], i ? pos[i - 1].y() + 3 : y + kTextHeight); x += widths[i]; } canvas->drawPosTextH(text, length, posX, y, paint); canvas->drawPosText(text, length, pos, paint); }
void SkSVGDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint) { SkPath path; switch (mode) { // todo case SkCanvas::kPoints_PointMode: SkDebugf("unsupported operation: drawPoints(kPoints_PointMode)\n"); break; case SkCanvas::kLines_PointMode: count -= 1; for (size_t i = 0; i < count; i += 2) { path.rewind(); path.moveTo(pts[i]); path.lineTo(pts[i+1]); AutoElement elem("path", fWriter, fResourceBucket, draw, paint); elem.addPathAttributes(path); } break; case SkCanvas::kPolygon_PointMode: if (count > 1) { path.addPoly(pts, SkToInt(count), false); path.moveTo(pts[0]); AutoElement elem("path", fWriter, fResourceBucket, draw, paint); elem.addPathAttributes(path); } break; } }
void onOnceBeforeDraw() override { for (size_t i = 0; i < SK_ARRAY_COUNT(fTallBmps); ++i) { int h = SkToInt((4 + i) * 1024); fTallBmps[i].fItemCnt = make_bm(&fTallBmps[i].fBmp, h); } }
DEF_TEST(TextBlob_extended, reporter) { SkTextBlobBuilder textBlobBuilder; SkFont font; const char text1[] = "Foo"; const char text2[] = "Bar"; int glyphCount = font.countText(text1, strlen(text1), SkTextEncoding::kUTF8); SkAutoTMalloc<uint16_t> glyphs(glyphCount); (void)font.textToGlyphs(text1, strlen(text1), SkTextEncoding::kUTF8, glyphs.get(), glyphCount); auto run = SkTextBlobBuilderPriv::AllocRunText(&textBlobBuilder, font, glyphCount, 0, 0, SkToInt(strlen(text2)), SkString(), nullptr); memcpy(run.glyphs, glyphs.get(), sizeof(uint16_t) * glyphCount); memcpy(run.utf8text, text2, strlen(text2)); for (int i = 0; i < glyphCount; ++i) { run.clusters[i] = SkTMin(SkToU32(i), SkToU32(strlen(text2))); } sk_sp<SkTextBlob> blob(textBlobBuilder.make()); REPORTER_ASSERT(reporter, blob); for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) { REPORTER_ASSERT(reporter, it.glyphCount() == (uint32_t)glyphCount); for (uint32_t i = 0; i < it.glyphCount(); ++i) { REPORTER_ASSERT(reporter, it.glyphs()[i] == glyphs[i]); } REPORTER_ASSERT(reporter, SkTextBlobRunIterator::kDefault_Positioning == it.positioning()); REPORTER_ASSERT(reporter, (SkPoint{0.0f, 0.0f}) == it.offset()); REPORTER_ASSERT(reporter, it.textSize() > 0); REPORTER_ASSERT(reporter, it.clusters()); for (uint32_t i = 0; i < it.glyphCount(); ++i) { REPORTER_ASSERT(reporter, i == it.clusters()[i]); } REPORTER_ASSERT(reporter, 0 == strncmp(text2, it.text(), it.textSize())); } }
virtual void onDraw(SkCanvas* canvas) { // explicitly add spaces, to test a prev. bug const char* text = "Ham bur ge fons"; int len = SkToInt(strlen(text)); SkPath path; SkPaint paint; paint.setAntiAlias(true); paint.setTextSize(SkIntToScalar(48)); canvas->translate(SkIntToScalar(10), SkIntToScalar(64)); canvas->drawText(text, len, 0, 0, paint); paint.getTextPath(text, len, 0, 0, &path); strokePath(canvas, path); path.reset(); SkAutoTArray<SkPoint> pos(len); SkAutoTArray<SkScalar> widths(len); paint.getTextWidths(text, len, &widths[0]); SkLCGRandom rand; SkScalar x = SkIntToScalar(20); SkScalar y = SkIntToScalar(100); for (int i = 0; i < len; ++i) { pos[i].set(x, y + rand.nextSScalar1() * 24); x += widths[i]; } canvas->translate(0, SkIntToScalar(64)); canvas->drawPosText(text, len, &pos[0], paint); paint.getPosTextPath(text, len, &pos[0], &path); strokePath(canvas, path); }
// Returns a deterministic data of the given size that should be // very compressible. static SkData* new_test_data(size_t dataSize) { SkAutoTMalloc<uint8_t> testBuffer(dataSize); for (size_t i = 0; i < dataSize; ++i) { testBuffer[SkToInt(i)] = i % 64; } return SkData::NewFromMalloc(testBuffer.detach(), dataSize); }
static void GenKey(const GrProcessor& processor, GrProcessorKeyBuilder* b) { const PorterDuffXferProcessor& xp = processor.cast<PorterDuffXferProcessor>(); b->add32(SkToInt(xp.readsCoverage()) | (xp.getBlendFormula().fPrimaryOutputType << 1) | (xp.getBlendFormula().fSecondaryOutputType << 4)); GR_STATIC_ASSERT(BlendFormula::kLast_OutputType < 8); };
static size_t uni_to_utf8(const SkUnichar src[], void* dst, int count) { char* u8 = (char*)dst; for (int i = 0; i < count; ++i) { int n = SkToInt(SkUTF8_FromUnichar(src[i], u8)); u8 += n; } return u8 - (char*)dst; }
// Convert ETC1 functions to our function signatures static bool compress_etc1_565(uint8_t* dst, const uint8_t* src, int width, int height, size_t rowBytes) { #ifndef SK_IGNORE_ETC1_SUPPORT return 0 == etc1_encode_image(src, width, height, 2, SkToInt(rowBytes), dst); #else return false; #endif }
static size_t uni_to_utf16(const SkUnichar src[], void* dst, int count) { uint16_t* u16 = (uint16_t*)dst; for (int i = 0; i < count; ++i) { int n = SkToInt(SkUTF16_FromUnichar(src[i], u16)); u16 += n; } return (char*)u16 - (char*)dst; }
void SkPictureRecord::drawData(const void* data, size_t length) { // op + length + 'length' worth of data size_t size = 2 * kUInt32Size + SkAlign4(length); size_t initialOffset = this->addDraw(DRAW_DATA, &size); this->addInt(SkToInt(length)); fWriter.writePad(data, length); this->validate(initialOffset, size); }
SkFlattenable* SkDashPathEffect::CreateProc(SkReadBuffer& buffer) { const SkScalar phase = buffer.readScalar(); uint32_t count = buffer.getArrayCount(); SkAutoSTArray<32, SkScalar> intervals(count); if (buffer.readScalarArray(intervals.get(), count)) { return Create(intervals.get(), SkToInt(count), phase); } return nullptr; }
void GrResourceCache::dumpStats(SkString* out) const { this->validate(); Stats stats; this->getStats(&stats); float countUtilization = (100.f * fBudgetedCount) / fMaxCount; float byteUtilization = (100.f * fBudgetedBytes) / fMaxBytes; out->appendf("Budget: %d items %d bytes\n", fMaxCount, (int)fMaxBytes); out->appendf("\t\tEntry Count: current %d" " (%d budgeted, %d wrapped, %d locked, %d scratch %.2g%% full), high %d\n", stats.fTotal, fBudgetedCount, stats.fWrapped, stats.fNumNonPurgeable, stats.fScratch, countUtilization, fHighWaterCount); out->appendf("\t\tEntry Bytes: current %d (budgeted %d, %.2g%% full, %d unbudgeted) high %d\n", SkToInt(fBytes), SkToInt(fBudgetedBytes), byteUtilization, SkToInt(stats.fUnbudgetedSize), SkToInt(fHighWaterBytes)); }
// called by both write() and finalize() static void do_deflate(int flush, z_stream* zStream, SkWStream* out, unsigned char* inBuffer, size_t inBufferSize) { zStream->next_in = inBuffer; zStream->avail_in = SkToInt(inBufferSize); unsigned char outBuffer[SKDEFLATEWSTREAM_OUTPUT_BUFFER_SIZE]; SkDEBUGCODE(int returnValue;) do {
void SkRasterPipelineBlitter::maybe_shade(int x, int y, int w) { if (fBurstCtx) { if (w > SkToInt(fShaderBuffer.size())) { fShaderBuffer.resize(w); } fBurstCtx->shadeSpan4f(x,y, fShaderBuffer.data(), w); // We'll be reading from fShaderOutput + x. fShaderOutput = fShaderBuffer.data() - x; } }
// ReleaseProc for SkData, assuming the data was allocated via sk_malloc, and its contents are an // array of SkRefCnt* which need to be unref'd. // static void unref_all_malloc_releaseProc(const void* ptr, size_t length, void* context) { SkASSERT(ptr == context); // our context is our ptr, allocated via sk_malloc int count = SkToInt(length / sizeof(SkRefCnt*)); SkASSERT(count * sizeof(SkRefCnt*) == length); // our length is snug for the array SkRefCnt* const* array = reinterpret_cast<SkRefCnt* const*>(ptr); for (int i = 0; i < count; ++i) { SkSafeUnref(array[i]); } sk_free(context); }
void SkPictureRecord::onDrawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint) { // op + paint index + mode + count + point data size_t size = 4 * kUInt32Size + count * sizeof(SkPoint); size_t initialOffset = this->addDraw(DRAW_POINTS, &size); this->addPaint(paint); this->addInt(mode); this->addInt(SkToInt(count)); fWriter.writeMul4(pts, count * sizeof(SkPoint)); this->validate(initialOffset, size); }
std::unique_ptr<SkAdvancedTypefaceMetrics> SkTestTypeface::onGetAdvancedMetrics() const { // pdf only std::unique_ptr<SkAdvancedTypefaceMetrics> info(new SkAdvancedTypefaceMetrics); info->fFontName.set(fTestFont->fName); int glyphCount = this->onCountGlyphs(); SkTDArray<SkUnichar>& toUnicode = info->fGlyphToUnicode; toUnicode.setCount(glyphCount); SkASSERT(glyphCount == SkToInt(fTestFont->fCharCodesCount)); for (int gid = 0; gid < glyphCount; ++gid) { toUnicode[gid] = SkToS32(fTestFont->fCharCodes[gid]); } return info; }
static SkString pdf_date(const SkTime::DateTime& dt) { int timeZoneMinutes = SkToInt(dt.fTimeZoneMinutes); char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-'; int timeZoneHours = SkTAbs(timeZoneMinutes) / 60; timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60; return SkStringPrintf( "D:%04u%02u%02u%02u%02u%02u%c%02d'%02d'", static_cast<unsigned>(dt.fYear), static_cast<unsigned>(dt.fMonth), static_cast<unsigned>(dt.fDay), static_cast<unsigned>(dt.fHour), static_cast<unsigned>(dt.fMinute), static_cast<unsigned>(dt.fSecond), timezoneSign, timeZoneHours, timeZoneMinutes); }
void SkTime::DateTime::toISO8601(SkString* dst) const { if (dst) { int timeZoneMinutes = SkToInt(fTimeZoneMinutes); char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-'; int timeZoneHours = SkTAbs(timeZoneMinutes) / 60; timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60; dst->printf("%04u-%02u-%02uT%02u:%02u:%02u%c%02d:%02d", static_cast<unsigned>(fYear), static_cast<unsigned>(fMonth), static_cast<unsigned>(fDay), static_cast<unsigned>(fHour), static_cast<unsigned>(fMinute), static_cast<unsigned>(fSecond), timezoneSign, timeZoneHours, timeZoneMinutes); } }
GrPathRange* GrGLPathRendering::createGlyphs(const SkTypeface* typeface, const SkDescriptor* desc, const SkStrokeRec& stroke) { if (NULL != desc || !caps().glyphLoadingSupport) { return GrPathRendering::createGlyphs(typeface, desc, stroke); } if (NULL == typeface) { typeface = SkTypeface::GetDefaultTypeface(); SkASSERT(NULL != typeface); } int faceIndex; SkAutoTDelete<SkStream> fontStream(typeface->openStream(&faceIndex)); const size_t fontDataLength = fontStream->getLength(); if (0 == fontDataLength) { return GrPathRendering::createGlyphs(typeface, NULL, stroke); } SkTArray<uint8_t> fontTempBuffer; const void* fontData = fontStream->getMemoryBase(); if (NULL == fontData) { // TODO: Find a more efficient way to pass the font data (e.g. open file descriptor). fontTempBuffer.reset(SkToInt(fontDataLength)); fontStream->read(&fontTempBuffer.front(), fontDataLength); fontData = &fontTempBuffer.front(); } const int numPaths = typeface->countGlyphs(); const GrGLuint basePathID = this->genPaths(numPaths); SkAutoTUnref<GrGLPath> templatePath(SkNEW_ARGS(GrGLPath, (fGpu, SkPath(), stroke))); GrGLenum status; GL_CALL_RET(status, PathMemoryGlyphIndexArray(basePathID, GR_GL_STANDARD_FONT_FORMAT, fontDataLength, fontData, faceIndex, 0, numPaths, templatePath->pathID(), SkPaint::kCanonicalTextSizeForPaths)); if (GR_GL_FONT_GLYPHS_AVAILABLE != status) { this->deletePaths(basePathID, numPaths); return GrPathRendering::createGlyphs(typeface, NULL, stroke); } // This is a crude approximation. We may want to consider giving this class // a pseudo PathGenerator whose sole purpose is to track the approximate gpu // memory size. const size_t gpuMemorySize = fontDataLength / 4; return SkNEW_ARGS(GrGLPathRange, (fGpu, basePathID, numPaths, gpuMemorySize, stroke)); }
void SkPictureRecord::onDrawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint) { fContentInfo.onDrawPoints(count, paint); // op + paint index + mode + count + point data size_t size = 4 * kUInt32Size + count * sizeof(SkPoint); size_t initialOffset = this->addDraw(DRAW_POINTS, &size); SkASSERT(initialOffset+get_paint_offset(DRAW_POINTS, size) == fWriter.bytesWritten()); this->addPaint(paint); this->addInt(mode); this->addInt(SkToInt(count)); fWriter.writeMul4(pts, count * sizeof(SkPoint)); this->validate(initialOffset, size); }
sk_sp<SkFlattenable> SkDashImpl::CreateProc(SkReadBuffer& buffer) { const SkScalar phase = buffer.readScalar(); uint32_t count = buffer.getArrayCount(); // Don't allocate gigantic buffers if there's not data for them. if (count > buffer.size() / sizeof(SkScalar)) { return nullptr; } SkAutoSTArray<32, SkScalar> intervals(count); if (buffer.readScalarArray(intervals.get(), count)) { return SkDashPathEffect::Make(intervals.get(), SkToInt(count), phase); } return nullptr; }
SkRect draw(SkCanvas* canvas, const SkPaint& paint) override { SkRandom random; SkPoint points[500]; SkRect bounds = SkRect::MakeWH(50, 50); int count = SkToInt(SK_ARRAY_COUNT(points)); if (SkCanvas::kPoints_PointMode != fMode) { count = SkTMin(count, 10); } for (int p = 0; p < count; ++p) { points[p].fX = random.nextUScalar1() * bounds.width(); points[p].fY = random.nextUScalar1() * bounds.width(); } canvas->drawPoints(fMode, count, points, paint); return bounds; }
void SkErrorInternals::SetError(SkError code, const char *fmt, ...) { THREAD_ERROR = code; va_list args; char *str = THREAD_ERROR_STRING; const char *error_name = NULL; switch( code ) { case kNoError_SkError: error_name = "No Error"; break; case kInvalidArgument_SkError: error_name = "Invalid Argument"; break; case kInvalidOperation_SkError: error_name = "Invalid Operation"; break; case kInvalidHandle_SkError: error_name = "Invalid Handle"; break; case kInvalidPaint_SkError: error_name = "Invalid Paint"; break; case kOutOfMemory_SkError: error_name = "Out Of Memory"; break; case kParseError_SkError: error_name = "Parse Error"; break; default: error_name = "Unknown error"; break; } sprintf( str, "%s: ", error_name ); int string_left = SkToInt(ERROR_STRING_LENGTH - strlen(str)); str += strlen(str); va_start( args, fmt ); vsnprintf( str, string_left, fmt, args ); va_end( args ); SkErrorCallbackFunction fn = THREAD_ERROR_CALLBACK; if (fn && code != kNoError_SkError) { fn(code, THREAD_ERROR_CONTEXT); } }
// Sanity checks for the GetDateTime function. DEF_TEST(Time_GetDateTime, r) { SkTime::DateTime dateTime; SkTime::GetDateTime(&dateTime); // TODO(future generation): update these values. const uint16_t kMinimumSaneYear = 1964; const uint16_t kMaximumSaneYear = 2064; if (dateTime.fYear < kMinimumSaneYear) { ERRORF(r, "SkTime::GetDateTime: %u (CurrentYear) < %u (MinimumSaneYear)", static_cast<unsigned>(dateTime.fYear), static_cast<unsigned>(kMinimumSaneYear)); } if (dateTime.fYear > kMaximumSaneYear) { ERRORF(r, "SkTime::GetDateTime: %u (CurrentYear) > %u (MaximumSaneYear)", static_cast<unsigned>(dateTime.fYear), static_cast<unsigned>(kMaximumSaneYear)); } REPORTER_ASSERT(r, dateTime.fMonth >= 1); REPORTER_ASSERT(r, dateTime.fMonth <= 12); REPORTER_ASSERT(r, dateTime.fDay >= 1); REPORTER_ASSERT(r, dateTime.fDay <= 31); REPORTER_ASSERT(r, dateTime.fHour <= 23); REPORTER_ASSERT(r, dateTime.fMinute <= 59); REPORTER_ASSERT(r, dateTime.fSecond <= 60); // leap seconds are 23:59:60 // The westernmost timezone is -12:00. // The easternmost timezone is +14:00. REPORTER_ASSERT(r, SkTAbs(SkToInt(dateTime.fTimeZoneMinutes)) <= 14 * 60); SkString timeStamp; dateTime.toISO8601(&timeStamp); REPORTER_ASSERT(r, timeStamp.size() > 0); INFOF(r, "\nCurrent Time (ISO-8601 format): \"%s\"\n", timeStamp.c_str()); }