diff options
-rw-r--r-- | gm/spritebitmap.cpp | 118 | ||||
-rw-r--r-- | include/core/SkDevice.h | 2 | ||||
-rw-r--r-- | include/core/SkImage.h | 22 | ||||
-rw-r--r-- | include/core/SkImageFilter.h | 20 | ||||
-rw-r--r-- | src/core/SkCanvas.cpp | 4 | ||||
-rw-r--r-- | src/core/SkImageFilter.cpp | 4 | ||||
-rw-r--r-- | src/gpu/GrLayerHoister.cpp | 2 | ||||
-rw-r--r-- | src/gpu/SkGpuDevice.cpp | 2 | ||||
-rw-r--r-- | src/image/SkImage.cpp | 75 | ||||
-rw-r--r-- | src/image/SkImage_Base.h | 7 | ||||
-rw-r--r-- | src/image/SkImage_Gpu.cpp | 70 | ||||
-rw-r--r-- | src/image/SkImage_Gpu.h | 6 | ||||
-rw-r--r-- | tests/ImageFilterTest.cpp | 14 |
13 files changed, 326 insertions, 20 deletions
diff --git a/gm/spritebitmap.cpp b/gm/spritebitmap.cpp index ceed50acaa..0d467ea01a 100644 --- a/gm/spritebitmap.cpp +++ b/gm/spritebitmap.cpp @@ -97,3 +97,121 @@ private: }; DEF_GM( return new SpriteBitmapGM; ) +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "SkColorFilterImageFilter.h" +#include "SkModeColorFilter.h" +#include "SkMorphologyImageFilter.h" +#include "SkOffsetImageFilter.h" + +static SkImage* make_image(SkCanvas* rootCanvas) { + SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100); + SkAutoTUnref<SkSurface> surface(rootCanvas->newSurface(info)); + if (!surface) { + surface.reset(SkSurface::NewRaster(info)); + } + + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(SK_ColorRED); + surface->getCanvas()->drawCircle(50, 50, 50, paint); + return surface->newImageSnapshot(); +} + +static void show_image(SkCanvas* canvas, SkImage* image, const SkIPoint& offset) { + SkScalar x = SkIntToScalar(offset.x()); + SkScalar y = SkIntToScalar(offset.y()); + + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + + SkRect r = SkRect::MakeIWH(image->width(), image->height()); + r.offset(x, y); + // get on pixel-centers to make the hairline land on a numerical stable boundary + r.outset(SK_ScalarHalf, SK_ScalarHalf); + canvas->drawRect(r, paint); + + canvas->drawImage(image, x, y, nullptr); +} + +typedef SkImageFilter* (*ImageFilterFactory)(); + +// +[]{...} did not work on windows (VS) +// (ImageFilterFactory)[]{...} did not work on linux (gcc) +// hence this cast function +template <typename T> ImageFilterFactory IFCCast(T arg) { return arg; } + +/** + * Compare output of drawSprite and drawBitmap (esp. clipping and imagefilters) + */ +class ApplyFilterGM : public skiagm::GM { +public: + ApplyFilterGM() {} + +protected: + SkString onShortName() override { + return SkString("apply-filter"); + } + + SkISize onISize() override { + return SkISize::Make(640, 480); + } + + void onDraw(SkCanvas* canvas) override { + SkAutoTUnref<SkImage> image0(make_image(canvas)); + + const ImageFilterFactory factories[] = { + IFCCast([]{ return SkBlurImageFilter::Create(8, 8); }), + IFCCast([]{ SkAutoTUnref<SkColorFilter> cf(SkModeColorFilter::Create(SK_ColorBLUE, + SkXfermode::kSrcIn_Mode)); + return SkColorFilterImageFilter::Create(cf); + }), + IFCCast([]{ return SkDilateImageFilter::Create(8, 8); }), + IFCCast([]{ return SkErodeImageFilter::Create(8, 8); }), + IFCCast([]{ return SkOffsetImageFilter::Create(8, 8); }), + }; + + const SkScalar spacer = image0->width() * 3.0f / 2; + + for (auto&& factory : factories) { + SkAutoTUnref<SkImageFilter> filter(factory()); + + SkIPoint offset1, offset2; + SkAutoTUnref<SkImage> image1(image0->applyFilter(filter, &offset1, true)); + SkAutoTUnref<SkImage> image2(image0->applyFilter(filter, &offset2, false)); + + canvas->save(); + canvas->translate(30, 30); + show_image(canvas, image0, SkIPoint::Make(0, 0)); // original + canvas->translate(spacer, 0); + show_image(canvas, image1, offset1); // snug + canvas->translate(spacer, 0); + show_image(canvas, image2, offset2); // not snug + + // Try drawing the original w/ the filter, to see that it "draws" the same as + // when we have manually applied the filter (above). + { + SkPaint paint; + paint.setImageFilter(filter); + + SkBitmap bm; + image0->asLegacyBitmap(&bm, SkImage::kRO_LegacyBitmapMode); + SkPoint loc = { 0, 0 }; + canvas->translate(spacer, 0); + canvas->getTotalMatrix().mapPoints(&loc, 1); + canvas->drawSprite(bm, (int)loc.x(), (int)loc.y(), &paint); // like snug + + canvas->translate(spacer, 0); + canvas->drawImage(image0, 0, 0, &paint); // like not snug + } + canvas->restore(); + + canvas->translate(0, spacer); + } + } + +private: + typedef GM INHERITED; +}; +DEF_GM( return new ApplyFilterGM; ) + diff --git a/include/core/SkDevice.h b/include/core/SkDevice.h index 9e01e62649..c52c579424 100644 --- a/include/core/SkDevice.h +++ b/include/core/SkDevice.h @@ -372,7 +372,7 @@ private: friend class SkDraw; friend class SkDrawIter; friend class SkDeviceFilteredPaint; - friend class SkImageFilter::Proxy; + friend class SkImageFilter::DeviceProxy; friend class SkNoPixelsBitmapDevice; friend class SkSurface_Raster; diff --git a/include/core/SkImage.h b/include/core/SkImage.h index 96de89ef87..9e3810c44f 100644 --- a/include/core/SkImage.h +++ b/include/core/SkImage.h @@ -304,6 +304,28 @@ public: */ bool isLazyGenerated() const; + /** + * Apply the specified filter to this image, and return the result as a new image. + * + * if forceResultToOriginalSize is true, then the resulting image will be the same size as the + * src, regardless of the normal output of the filter. + * + * If offset is non-null, it is set to the relative offset needed to draw the resulting image + * in the same logical place as the original. + * + * e.g. + * If the filter makes the result larger by a margin of 4 the output would be: + * result->width() = this->width + 8 + * result->height() = this->height + 8 + * offset.x() == -4 + * offset.y() == -4 + * + * If the filter fails to create a resulting image, null is returned, and the offset parameter + * (if specified) will be undefined. + */ + SkImage* applyFilter(SkImageFilter* filter, SkIPoint* offset, + bool forceResultToOriginalSize) const; + protected: SkImage(int width, int height, uint32_t uniqueID); diff --git a/include/core/SkImageFilter.h b/include/core/SkImageFilter.h index 30265e4fdd..326ae5bdaf 100644 --- a/include/core/SkImageFilter.h +++ b/include/core/SkImageFilter.h @@ -95,20 +95,32 @@ public: class Proxy { public: - Proxy(SkBaseDevice* device) : fDevice(device) { } + virtual ~Proxy() {} - SkBaseDevice* createDevice(int width, int height); + virtual SkBaseDevice* createDevice(int width, int height) = 0; + + // Returns true if the proxy handled the filter itself. If this returns + // false then the filter's code will be called. + virtual bool filterImage(const SkImageFilter*, const SkBitmap& src, + const SkImageFilter::Context&, + SkBitmap* result, SkIPoint* offset) = 0; + }; + + class DeviceProxy : public Proxy { + public: + DeviceProxy(SkBaseDevice* device) : fDevice(device) {} + + SkBaseDevice* createDevice(int width, int height) override; // Returns true if the proxy handled the filter itself. If this returns // false then the filter's code will be called. bool filterImage(const SkImageFilter*, const SkBitmap& src, const SkImageFilter::Context&, - SkBitmap* result, SkIPoint* offset); + SkBitmap* result, SkIPoint* offset) override; private: SkBaseDevice* fDevice; }; - /** * Request a new (result) image to be created from the src image. * If the src has no pixels (isNull()) then the request just wants to diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp index 4952ae2581..6914963cb9 100644 --- a/src/core/SkCanvas.cpp +++ b/src/core/SkCanvas.cpp @@ -1300,7 +1300,7 @@ void SkCanvas::internalDrawDevice(SkBaseDevice* srcDev, int x, int y, SkImageFilter* filter = paint->getImageFilter(); SkIPoint pos = { x - iter.getX(), y - iter.getY() }; if (filter && !dstDev->canHandleImageFilter(filter)) { - SkImageFilter::Proxy proxy(dstDev); + SkImageFilter::DeviceProxy proxy(dstDev); SkBitmap dst; SkIPoint offset = SkIPoint::Make(0, 0); const SkBitmap& src = srcDev->accessBitmap(false); @@ -1352,7 +1352,7 @@ void SkCanvas::onDrawSprite(const SkBitmap& bitmap, int x, int y, const SkPaint* SkImageFilter* filter = paint->getImageFilter(); SkIPoint pos = { x - iter.getX(), y - iter.getY() }; if (filter && !iter.fDevice->canHandleImageFilter(filter)) { - SkImageFilter::Proxy proxy(iter.fDevice); + SkImageFilter::DeviceProxy proxy(iter.fDevice); SkBitmap dst; SkIPoint offset = SkIPoint::Make(0, 0); SkMatrix matrix = *iter.fMatrix; diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp index 4ad7f0eeba..08d46c7970 100644 --- a/src/core/SkImageFilter.cpp +++ b/src/core/SkImageFilter.cpp @@ -578,7 +578,7 @@ void SkImageFilter::PurgeCache() { /////////////////////////////////////////////////////////////////////////////////////////////////// -SkBaseDevice* SkImageFilter::Proxy::createDevice(int w, int h) { +SkBaseDevice* SkImageFilter::DeviceProxy::createDevice(int w, int h) { SkBaseDevice::CreateInfo cinfo(SkImageInfo::MakeN32Premul(w, h), SkBaseDevice::kNever_TileUsage, kUnknown_SkPixelGeometry, @@ -592,7 +592,7 @@ SkBaseDevice* SkImageFilter::Proxy::createDevice(int w, int h) { return dev; } -bool SkImageFilter::Proxy::filterImage(const SkImageFilter* filter, const SkBitmap& src, +bool SkImageFilter::DeviceProxy::filterImage(const SkImageFilter* filter, const SkBitmap& src, const SkImageFilter::Context& ctx, SkBitmap* result, SkIPoint* offset) { return fDevice->filterImage(filter, src, ctx, result, offset); diff --git a/src/gpu/GrLayerHoister.cpp b/src/gpu/GrLayerHoister.cpp index baeb3784ed..5bddc036df 100644 --- a/src/gpu/GrLayerHoister.cpp +++ b/src/gpu/GrLayerHoister.cpp @@ -313,7 +313,7 @@ void GrLayerHoister::FilterLayer(GrContext* context, SkAutoTUnref<SkImageFilter::Cache> cache(SkImageFilter::Cache::Create(kDefaultCacheSize)); SkImageFilter::Context filterContext(totMat, clipBounds, cache); - SkImageFilter::Proxy proxy(device); + SkImageFilter::DeviceProxy proxy(device); const SkBitmap src = wrap_texture(layer->texture()); if (!layer->filter()->filterImage(&proxy, src, filterContext, &filteredBitmap, &offset)) { diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp index 4d150599a1..ab913164ee 100644 --- a/src/gpu/SkGpuDevice.cpp +++ b/src/gpu/SkGpuDevice.cpp @@ -1395,7 +1395,7 @@ bool SkGpuDevice::filterTexture(GrContext* context, GrTexture* texture, SkBitmap* result, SkIPoint* offset) { SkASSERT(filter); - SkImageFilter::Proxy proxy(this); + SkImageFilter::DeviceProxy proxy(this); if (filter->canFilterImageGPU()) { return filter->filterImageGPU(&proxy, wrap_texture(texture, width, height), diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp index af9c27592c..3ad80043da 100644 --- a/src/image/SkImage.cpp +++ b/src/image/SkImage.cpp @@ -67,6 +67,81 @@ void SkImage::preroll(GrContext* ctx) const { } } +SkImage* SkImage::applyFilter(SkImageFilter* filter, SkIPoint* offset, + bool forceResultToOriginalSize) const { + if (!filter) { + return nullptr; + } + + SkIPoint offsetStorage; + if (!offset) { + offset = &offsetStorage; + } + return as_IB(this)->onApplyFilter(filter, offset, forceResultToOriginalSize); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "SkImageFilter.h" +#include "SkBitmapDevice.h" + +static SkIRect compute_fast_ibounds(SkImageFilter* filter, const SkIRect& srcBounds) { + SkRect fastBounds; + fastBounds.set(srcBounds); + filter->computeFastBounds(fastBounds, &fastBounds); + return fastBounds.roundOut(); +} + +class SkRasterImageFilterProxy : public SkImageFilter::Proxy { +public: + SkBaseDevice* createDevice(int width, int height) override { + return SkBitmapDevice::Create(SkImageInfo::MakeN32Premul(width, height)); + } + + bool filterImage(const SkImageFilter*, const SkBitmap&, const SkImageFilter::Context&, + SkBitmap*, SkIPoint*) override { + return false; + } +}; + +SkImage* SkImage_Base::onApplyFilter(SkImageFilter* filter, SkIPoint* offsetResult, + bool forceResultToOriginalSize) const { + SkBitmap src; + if (!this->getROPixels(&src)) { + return nullptr; + } + + const SkIRect srcBounds = SkIRect::MakeWH(this->width(), this->height()); + + if (forceResultToOriginalSize) { + const SkIRect clipBounds = srcBounds; + SkRasterImageFilterProxy proxy; + SkImageFilter::Context ctx(SkMatrix::I(), clipBounds, SkImageFilter::Cache::Get()); + + SkBitmap dst; + if (filter->filterImage(&proxy, src, ctx, &dst, offsetResult)) { + dst.setImmutable(); + return SkImage::NewFromBitmap(dst); + } + } else { + const SkIRect dstR = compute_fast_ibounds(filter, srcBounds); + + SkImageInfo info = SkImageInfo::MakeN32Premul(dstR.width(), dstR.height()); + SkAutoTUnref<SkSurface> surface(this->onNewSurface(info)); + + SkPaint paint; + paint.setImageFilter(filter); + surface->getCanvas()->drawImage(this, SkIntToScalar(-dstR.x()), SkIntToScalar(-dstR.y()), + &paint); + + offsetResult->set(dstR.x(), dstR.y()); + return surface->newImageSnapshot(); + } + return nullptr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + SkShader* SkImage::newShader(SkShader::TileMode tileX, SkShader::TileMode tileY, const SkMatrix* localMatrix) const { diff --git a/src/image/SkImage_Base.h b/src/image/SkImage_Base.h index 7819e9c19d..3fe3444aef 100644 --- a/src/image/SkImage_Base.h +++ b/src/image/SkImage_Base.h @@ -37,6 +37,13 @@ public: // but only inspect them (or encode them). virtual bool getROPixels(SkBitmap*) const = 0; + virtual SkImage* onApplyFilter(SkImageFilter*, SkIPoint* offset, + bool forceResultToOriginalSize) const; + + virtual SkSurface* onNewSurface(const SkImageInfo& info) const { + return SkSurface::NewRaster(info); + } + // Caller must call unref when they are done. virtual GrTexture* asTextureRef(GrContext*, const GrTextureParams&) const = 0; diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp index ccb27eed83..529df3abbd 100644 --- a/src/image/SkImage_Gpu.cpp +++ b/src/image/SkImage_Gpu.cpp @@ -38,6 +38,10 @@ extern void SkTextureImageApplyBudgetedDecision(SkImage* image) { } } +static SkImageInfo make_info(int w, int h, bool isOpaque) { + return SkImageInfo::MakeN32(w, h, isOpaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType); +} + bool SkImage_Gpu::getROPixels(SkBitmap* dst) const { if (SkBitmapCache::Find(this->uniqueID(), dst)) { SkASSERT(dst->getGenerationID() == this->uniqueID()); @@ -46,8 +50,7 @@ bool SkImage_Gpu::getROPixels(SkBitmap* dst) const { return true; } - SkAlphaType at = this->isOpaque() ? kOpaque_SkAlphaType : kPremul_SkAlphaType; - if (!dst->tryAllocPixels(SkImageInfo::MakeN32(this->width(), this->height(), at))) { + if (!dst->tryAllocPixels(make_info(this->width(), this->height(), this->isOpaque()))) { return false; } if (!fTexture->readPixels(0, 0, dst->width(), dst->height(), kSkia8888_GrPixelConfig, @@ -183,6 +186,69 @@ SkImage* SkImage_Gpu::onNewSubset(const SkIRect& subset) const { /////////////////////////////////////////////////////////////////////////////////////////////////// +#include "SkBitmapDevice.h" +#include "SkGrPixelRef.h" +#include "SkImageFilter.h" + +class SkGpuImageFilterProxy : public SkImageFilter::Proxy { + GrContext* fCtx; + +public: + SkGpuImageFilterProxy(GrContext* ctx) : fCtx(ctx) {} + + SkBaseDevice* createDevice(int width, int height) override { + GrSurfaceDesc desc; + desc.fConfig = kSkia8888_GrPixelConfig; + desc.fFlags = kRenderTarget_GrSurfaceFlag; + desc.fWidth = width; + desc.fHeight = height; + desc.fSampleCnt = 0; + + SkAutoTUnref<GrTexture> texture(fCtx->textureProvider()->createTexture(desc, true)); + + if (texture) { + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + return SkGpuDevice::Create(texture->asRenderTarget(), width, height, &props, + SkGpuDevice::kClear_InitContents); + } else { + return nullptr; + } + } + + bool filterImage(const SkImageFilter*, const SkBitmap&, const SkImageFilter::Context&, + SkBitmap*, SkIPoint*) override { + return false; + } +}; + +SkImage* SkImage_Gpu::onApplyFilter(SkImageFilter* filter, SkIPoint* offsetResult, + bool forceResultToOriginalSize) const { + if (!forceResultToOriginalSize || !filter->canFilterImageGPU()) { + return this->INHERITED::onApplyFilter(filter, offsetResult, forceResultToOriginalSize); + } + + const SkImageInfo info = make_info(this->width(), this->height(), this->isOpaque()); + SkAutoTUnref<SkGrPixelRef> pr(new SkGrPixelRef(info, fTexture)); + SkBitmap src; + src.setInfo(info); + src.setPixelRef(pr, 0, 0); + + GrContext* context = fTexture->getContext(); + SkGpuImageFilterProxy proxy(context); + SkImageFilter::Context ctx(SkMatrix::I(), + SkIRect::MakeWH(this->width(), this->height()), + SkImageFilter::Cache::Get()); + + SkBitmap dst; + if (filter->filterImageGPU(&proxy, src, ctx, &dst, offsetResult)) { + return new SkImage_Gpu(dst.width(), dst.height(), kNeedNewImageUniqueID, info.alphaType(), + dst.getTexture(), SkSurface::kNo_Budgeted); + } + return nullptr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + static SkImage* new_wrapped_texture_common(GrContext* ctx, const GrBackendTextureDesc& desc, SkAlphaType at, GrWrapOwnership ownership, SkImage::TextureReleaseProc releaseProc, diff --git a/src/image/SkImage_Gpu.h b/src/image/SkImage_Gpu.h index 530c0445d3..d5080d7a3a 100644 --- a/src/image/SkImage_Gpu.h +++ b/src/image/SkImage_Gpu.h @@ -43,6 +43,12 @@ public: bool isOpaque() const override; bool onReadPixels(const SkImageInfo&, void* dstPixels, size_t dstRowBytes, int srcX, int srcY) const override; + SkImage* onApplyFilter(SkImageFilter*, SkIPoint* offset, + bool forceResultToOriginalSize) const override; + + SkSurface* onNewSurface(const SkImageInfo& info) const override { + return SkSurface::NewRenderTarget(fTexture->getContext(), SkSurface::kNo_Budgeted, info); + } private: SkAutoTUnref<GrTexture> fTexture; diff --git a/tests/ImageFilterTest.cpp b/tests/ImageFilterTest.cpp index 6b9064e3b4..b118296566 100644 --- a/tests/ImageFilterTest.cpp +++ b/tests/ImageFilterTest.cpp @@ -400,7 +400,7 @@ DEF_TEST(TestNegativeBlurSigma, reporter) { const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType); SkAutoTUnref<SkBaseDevice> device(SkBitmapDevice::Create(info, props)); - SkImageFilter::Proxy proxy(device); + SkImageFilter::DeviceProxy proxy(device); test_negative_blur_sigma(&proxy, reporter); } @@ -773,7 +773,7 @@ DEF_TEST(ImageFilterCropRect, reporter) { const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType); SkAutoTUnref<SkBaseDevice> device(SkBitmapDevice::Create(info, props)); - SkImageFilter::Proxy proxy(device); + SkImageFilter::DeviceProxy proxy(device); test_crop_rects(&proxy, reporter); } @@ -887,7 +887,7 @@ DEF_TEST(ImageFilterClippedPictureImageFilter, reporter) { bitmap.allocN32Pixels(2, 2); const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType); SkBitmapDevice device(bitmap, props); - SkImageFilter::Proxy proxy(&device); + SkImageFilter::DeviceProxy proxy(&device); REPORTER_ASSERT(reporter, !imageFilter->filterImage(&proxy, bitmap, ctx, &result, &offset)); } @@ -1125,7 +1125,7 @@ DEF_TEST(ComposedImageFilterOffset, reporter) { bitmap.eraseARGB(0, 0, 0, 0); const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType); SkBitmapDevice device(bitmap, props); - SkImageFilter::Proxy proxy(&device); + SkImageFilter::DeviceProxy proxy(&device); SkImageFilter::CropRect cropRect(SkRect::MakeXYWH(1, 0, 20, 20)); SkAutoTUnref<SkImageFilter> offsetFilter(SkOffsetImageFilter::Create(0, 0, nullptr, &cropRect)); @@ -1144,7 +1144,7 @@ DEF_TEST(PartialCropRect, reporter) { bitmap.eraseARGB(0, 0, 0, 0); const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType); SkBitmapDevice device(bitmap, props); - SkImageFilter::Proxy proxy(&device); + SkImageFilter::DeviceProxy proxy(&device); SkImageFilter::CropRect cropRect(SkRect::MakeXYWH(100, 0, 20, 30), SkImageFilter::CropRect::kHasWidth_CropEdge | SkImageFilter::CropRect::kHasHeight_CropEdge); @@ -1251,7 +1251,7 @@ DEF_GPUTEST(ImageFilterCropRectGPU, reporter, factory) { 0, &props, SkGpuDevice::kUninit_InitContents)); - SkImageFilter::Proxy proxy(device); + SkImageFilter::DeviceProxy proxy(device); test_crop_rects(&proxy, reporter); } @@ -1305,7 +1305,7 @@ DEF_GPUTEST(TestNegativeBlurSigmaGPU, reporter, factory) { 0, &props, SkGpuDevice::kUninit_InitContents)); - SkImageFilter::Proxy proxy(device); + SkImageFilter::DeviceProxy proxy(device); test_negative_blur_sigma(&proxy, reporter); } |