Enable flattening and unflattening of SkColorSpace
BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2085653003
Review-Url: https://codereview.chromium.org/2085653003
diff --git a/include/core/SkColorSpace.h b/include/core/SkColorSpace.h
index 4b55c47..57f74e9 100644
--- a/include/core/SkColorSpace.h
+++ b/include/core/SkColorSpace.h
@@ -11,6 +11,8 @@
#include "SkMatrix44.h"
#include "SkRefCnt.h"
+class SkData;
+
class SK_API SkColorSpace : public SkRefCnt {
public:
@@ -75,11 +77,17 @@
return kSRGB_GammaNamed == fGammaNamed || k2Dot2Curve_GammaNamed == fGammaNamed;
}
+ /**
+ * Returns nullptr on failure. Fails when we fallback to serializing ICC data and
+ * the data is too large to serialize.
+ */
+ sk_sp<SkData> serialize() const;
+
+ static sk_sp<SkColorSpace> Deserialize(const void* data, size_t length);
+
protected:
SkColorSpace(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Named named);
- friend Named sk_deduce_named_from_colorspace(SkColorSpace*);
-
const GammaNamed fGammaNamed;
const SkMatrix44 fToXYZD50;
const Named fNamed;
diff --git a/include/core/SkMatrix44.h b/include/core/SkMatrix44.h
index d64d4b7..715ee78 100644
--- a/include/core/SkMatrix44.h
+++ b/include/core/SkMatrix44.h
@@ -439,6 +439,15 @@
kAllPublic_Masks = 0xF
};
+ /** Efficiently reads 12 matrix entries, ignoring the last col.
+ * This is typically useful when we know the last col is (0, 0, 0, 1).
+ */
+ void as4x3ColMajorf(float[]) const;
+
+ /* This sets the top-left of the matrix and clears the
+ * perspective components (with [3][3] set to 1). */
+ void set4x3ColMajorf(const float[]);
+
SkMScalar transX() const { return fMat[3][0]; }
SkMScalar transY() const { return fMat[3][1]; }
SkMScalar transZ() const { return fMat[3][2]; }
@@ -469,6 +478,8 @@
inline bool isTriviallyIdentity() const {
return 0 == fTypeMask;
}
+
+ friend class SkColorSpace;
};
#endif
diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp
index 5df45ff..95c38f2 100644
--- a/src/core/SkColorSpace.cpp
+++ b/src/core/SkColorSpace.cpp
@@ -9,6 +9,8 @@
#include "SkColorSpace_Base.h"
#include "SkEndian.h"
#include "SkOnce.h"
+#include "SkReadBuffer.h"
+#include "SkWriteBuffer.h"
#define SkColorSpacePrintf(...)
@@ -24,11 +26,10 @@
, fNamed(named)
{}
-SkColorSpace_Base::SkColorSpace_Base(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Named named,
- sk_sp<SkData> profileData)
+SkColorSpace_Base::SkColorSpace_Base(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Named named)
: INHERITED(gammaNamed, toXYZD50, named)
, fGammas(nullptr)
- , fProfileData(std::move(profileData))
+ , fProfileData(nullptr)
{}
SkColorSpace_Base::SkColorSpace_Base(SkColorLookUpTable* colorLUT, sk_sp<SkGammas> gammas,
@@ -102,11 +103,10 @@
return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, gammas, toXYZD50, nullptr));
}
- return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50, nullptr);
+ return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50);
}
-sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(GammaNamed gammaNamed, const SkMatrix44& toXYZD50,
- sk_sp<SkData> profileData) {
+sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(GammaNamed gammaNamed, const SkMatrix44& toXYZD50) {
switch (gammaNamed) {
case kSRGB_GammaNamed:
if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) {
@@ -125,12 +125,11 @@
break;
}
- return sk_sp<SkColorSpace>(new SkColorSpace_Base(gammaNamed, toXYZD50, kUnknown_Named,
- profileData));
+ return sk_sp<SkColorSpace>(new SkColorSpace_Base(gammaNamed, toXYZD50, kUnknown_Named));
}
sk_sp<SkColorSpace> SkColorSpace::NewRGB(GammaNamed gammaNamed, const SkMatrix44& toXYZD50) {
- return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50, nullptr);
+ return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50);
}
sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
@@ -144,7 +143,7 @@
sRGBOnce([] {
SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
srgbToxyzD50.set3x3ColMajorf(gSRGB_toXYZD50);
- sRGB = new SkColorSpace_Base(kSRGB_GammaNamed, srgbToxyzD50, kSRGB_Named, nullptr);
+ sRGB = new SkColorSpace_Base(kSRGB_GammaNamed, srgbToxyzD50, kSRGB_Named);
});
return sk_ref_sp(sRGB);
}
@@ -153,7 +152,7 @@
SkMatrix44 adobergbToxyzD50(SkMatrix44::kUninitialized_Constructor);
adobergbToxyzD50.set3x3ColMajorf(gAdobeRGB_toXYZD50);
adobeRGB = new SkColorSpace_Base(k2Dot2Curve_GammaNamed, adobergbToxyzD50,
- kAdobeRGB_Named, nullptr);
+ kAdobeRGB_Named);
});
return sk_ref_sp(adobeRGB);
}
@@ -930,7 +929,7 @@
return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, std::move(gammas),
mat, std::move(data)));
} else {
- return SkColorSpace_Base::NewRGB(gammaNamed, mat, std::move(data));
+ return SkColorSpace_Base::NewRGB(gammaNamed, mat);
}
}
@@ -955,7 +954,7 @@
std::move(gammas), toXYZ,
std::move(data)));
} else {
- return SkColorSpace_Base::NewRGB(gammaNamed, toXYZ, std::move(data));
+ return SkColorSpace_Base::NewRGB(gammaNamed, toXYZ);
}
}
}
@@ -1208,3 +1207,153 @@
// the client calls again?
return SkData::MakeFromMalloc(profile.release(), kICCProfileSize);
}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+enum Version {
+ k0_Version, // Initial version, header + flags for matrix and profile
+};
+
+struct ColorSpaceHeader {
+ /**
+ * If kMatrix_Flag is set, we will write 12 floats after the header.
+ * Should not be set at the same time as the kICC_Flag.
+ */
+ static constexpr uint8_t kMatrix_Flag = 1 << 0;
+
+ /**
+ * If kICC_Flag is set, we will write an ICC profile after the header.
+ * The ICC profile will be written as a uint32 size, followed immediately
+ * by the data (padded to 4 bytes).
+ * Should not be set at the same time as the kMatrix_Flag.
+ */
+ static constexpr uint8_t kICC_Flag = 1 << 1;
+
+ static ColorSpaceHeader Pack(Version version, SkColorSpace::Named named,
+ SkColorSpace::GammaNamed gammaNamed, uint8_t flags) {
+ ColorSpaceHeader header;
+
+ SkASSERT(k0_Version == version);
+ header.fVersion = (uint8_t) version;
+
+ SkASSERT(named <= SkColorSpace::kAdobeRGB_Named);
+ header.fNamed = (uint8_t) named;
+
+ SkASSERT(gammaNamed <= SkColorSpace::kNonStandard_GammaNamed);
+ header.fGammaNamed = (uint8_t) gammaNamed;
+
+ SkASSERT(flags <= kICC_Flag);
+ header.fFlags = flags;
+ return header;
+ }
+
+ uint8_t fVersion; // Always zero
+ uint8_t fNamed; // Must be a SkColorSpace::Named
+ uint8_t fGammaNamed; // Must be a SkColorSpace::GammaNamed
+ uint8_t fFlags; // Some combination of the flags listed above
+};
+
+sk_sp<SkData> SkColorSpace::serialize() const {
+ // If we have a named profile, only write the enum.
+ switch (fNamed) {
+ case kSRGB_Named:
+ case kAdobeRGB_Named: {
+ sk_sp<SkData> data = SkData::MakeUninitialized(sizeof(ColorSpaceHeader));
+ *((ColorSpaceHeader*) data->writable_data()) =
+ ColorSpaceHeader::Pack(k0_Version, fNamed, fGammaNamed, 0);
+ return data;
+ }
+ default:
+ break;
+ }
+
+ // If we have a named gamma, write the enum and the matrix.
+ switch (fGammaNamed) {
+ case kSRGB_GammaNamed:
+ case k2Dot2Curve_GammaNamed:
+ case kLinear_GammaNamed: {
+ sk_sp<SkData> data = SkData::MakeUninitialized(sizeof(ColorSpaceHeader) +
+ 12 * sizeof(float));
+ void* dataPtr = data->writable_data();
+
+ *((ColorSpaceHeader*) dataPtr) = ColorSpaceHeader::Pack(k0_Version, fNamed, fGammaNamed,
+ ColorSpaceHeader::kMatrix_Flag);
+ dataPtr = SkTAddOffset<void>(dataPtr, sizeof(ColorSpaceHeader));
+
+ fToXYZD50.as4x3ColMajorf((float*) dataPtr);
+ return data;
+ }
+ default:
+ break;
+ }
+
+ // If we do not have a named gamma, this must have been created from an ICC profile.
+ // Since we were unable to recognize the gamma, we will have saved the ICC data.
+ SkASSERT(as_CSB(this)->fProfileData);
+
+ size_t profileSize = as_CSB(this)->fProfileData->size();
+ if (SkAlign4(profileSize) != (uint32_t) SkAlign4(profileSize)) {
+ return nullptr;
+ }
+
+ sk_sp<SkData> data = SkData::MakeUninitialized(sizeof(ColorSpaceHeader) + sizeof(uint32_t) +
+ SkAlign4(profileSize));
+ void* dataPtr = data->writable_data();
+
+ *((ColorSpaceHeader*) dataPtr) = ColorSpaceHeader::Pack(k0_Version, fNamed, fGammaNamed,
+ ColorSpaceHeader::kICC_Flag);
+ dataPtr = SkTAddOffset<void>(dataPtr, sizeof(ColorSpaceHeader));
+
+ *((uint32_t*) dataPtr) = (uint32_t) SkAlign4(profileSize);
+ dataPtr = SkTAddOffset<void>(dataPtr, sizeof(uint32_t));
+
+ memcpy(dataPtr, as_CSB(this)->fProfileData->data(), profileSize);
+ memset(SkTAddOffset<void>(dataPtr, profileSize), 0, SkAlign4(profileSize) - profileSize);
+ return data;
+}
+
+sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) {
+ if (length < sizeof(ColorSpaceHeader)) {
+ return nullptr;
+ }
+
+ ColorSpaceHeader header = *((const ColorSpaceHeader*) data);
+ data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader));
+ length -= sizeof(ColorSpaceHeader);
+ switch ((Named) header.fNamed) {
+ case kSRGB_Named:
+ case kAdobeRGB_Named:
+ return NewNamed((Named) header.fNamed);
+ default:
+ break;
+ }
+
+ switch ((GammaNamed) header.fGammaNamed) {
+ case kSRGB_GammaNamed:
+ case k2Dot2Curve_GammaNamed:
+ case kLinear_GammaNamed: {
+ if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) {
+ return nullptr;
+ }
+
+ SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
+ toXYZ.set4x3ColMajorf((const float*) data);
+ return NewRGB((GammaNamed) header.fGammaNamed, toXYZ);
+ }
+ default:
+ break;
+ }
+
+ if (ColorSpaceHeader::kICC_Flag != header.fFlags || length < sizeof(uint32_t)) {
+ return nullptr;
+ }
+
+ uint32_t profileSize = *((uint32_t*) data);
+ data = SkTAddOffset<const void>(data, sizeof(uint32_t));
+ length -= sizeof(uint32_t);
+ if (length < profileSize) {
+ return nullptr;
+ }
+
+ return NewICC(data, profileSize);
+}
diff --git a/src/core/SkColorSpace_Base.h b/src/core/SkColorSpace_Base.h
index fc4f665..9f63915 100644
--- a/src/core/SkColorSpace_Base.h
+++ b/src/core/SkColorSpace_Base.h
@@ -159,11 +159,9 @@
private:
- static sk_sp<SkColorSpace> NewRGB(GammaNamed gammaNamed, const SkMatrix44& toXYZD50,
- sk_sp<SkData> profileData);
+ static sk_sp<SkColorSpace> NewRGB(GammaNamed gammaNamed, const SkMatrix44& toXYZD50);
- SkColorSpace_Base(GammaNamed gammaNamed, const SkMatrix44& toXYZ, Named named,
- sk_sp<SkData> profileData);
+ SkColorSpace_Base(GammaNamed gammaNamed, const SkMatrix44& toXYZ, Named named);
SkColorSpace_Base(SkColorLookUpTable* colorLUT, sk_sp<SkGammas> gammas, const SkMatrix44& toXYZ,
sk_sp<SkData> profileData);
diff --git a/src/core/SkImageInfo.cpp b/src/core/SkImageInfo.cpp
index ee16923..ba418d5 100644
--- a/src/core/SkImageInfo.cpp
+++ b/src/core/SkImageInfo.cpp
@@ -9,50 +9,6 @@
#include "SkReadBuffer.h"
#include "SkWriteBuffer.h"
-/*
- * We store this as a byte in the ImageInfo flatten buffer.
- */
-enum class SkFlattenColorSpaceEnum {
- kUnspecified,
- kSRGB,
- kAdobe1998,
- // ... add more here
- kLastEnum = kAdobe1998,
- // final value means the actual profile data follows the info
- kICCProfile = 0xFF,
-};
-
-static sk_sp<SkColorSpace> make_from_enum(SkFlattenColorSpaceEnum value) {
- switch (value) {
- case SkFlattenColorSpaceEnum::kSRGB:
- return SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
- case SkFlattenColorSpaceEnum::kAdobe1998:
- return SkColorSpace::NewNamed(SkColorSpace::kAdobeRGB_Named);
- default:
- return nullptr;
- }
-}
-
-SkColorSpace::Named sk_deduce_named_from_colorspace(SkColorSpace* cs) {
- return cs->fNamed;
-}
-
-static SkFlattenColorSpaceEnum deduce_from_colorspace(SkColorSpace* cs) {
- if (!cs) {
- return SkFlattenColorSpaceEnum::kUnspecified;
- }
- switch (sk_deduce_named_from_colorspace(cs)) {
- case SkColorSpace::kSRGB_Named:
- return SkFlattenColorSpaceEnum::kSRGB;
- case SkColorSpace::kAdobeRGB_Named:
- return SkFlattenColorSpaceEnum::kAdobe1998;
- default:
- return SkFlattenColorSpaceEnum::kICCProfile;
- }
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
#ifdef SK_SUPPORT_LEGACY_COLORPROFILETYPE
SkColorProfileType SkImageInfo::profileType() const {
return fColorSpace && fColorSpace->gammaCloseToSRGB()
@@ -81,40 +37,31 @@
SkASSERT(0 == (packed >> 24));
fColorType = (SkColorType)((packed >> 0) & 0xFF);
fAlphaType = (SkAlphaType)((packed >> 8) & 0xFF);
- SkFlattenColorSpaceEnum csenum = (SkFlattenColorSpaceEnum)((packed >> 16) & 0xFF);
buffer.validate(alpha_type_is_valid(fAlphaType) && color_type_is_valid(fColorType));
- if (SkFlattenColorSpaceEnum::kICCProfile == csenum) {
- SkASSERT(false); // we shouldn't hit this yet, as we don't write these yet
- fColorSpace.reset();
- } else {
- if (csenum > SkFlattenColorSpaceEnum::kLastEnum) {
- csenum = SkFlattenColorSpaceEnum::kUnspecified;
- }
- fColorSpace = make_from_enum(csenum);
- }
+ sk_sp<SkData> data = buffer.readByteArrayAsData();
+ fColorSpace = SkColorSpace::Deserialize(data->data(), data->size());
}
void SkImageInfo::flatten(SkWriteBuffer& buffer) const {
buffer.write32(fWidth);
buffer.write32(fHeight);
- SkFlattenColorSpaceEnum csenum = deduce_from_colorspace(fColorSpace.get());
-
- // TODO: when we actually support flattening the colorspace to a profile blob, remove this
- // hack (and write the blob after we write packed.
- if (SkFlattenColorSpaceEnum::kICCProfile == csenum) {
- csenum = SkFlattenColorSpaceEnum::kUnspecified;
- }
-
- SkASSERT(0 == ((int)csenum & ~0xFF));
SkASSERT(0 == (fAlphaType & ~0xFF));
SkASSERT(0 == (fColorType & ~0xFF));
- uint32_t packed = ((int)csenum << 16) | (fAlphaType << 8) | fColorType;
+ uint32_t packed = (fAlphaType << 8) | fColorType;
buffer.write32(packed);
- if (SkFlattenColorSpaceEnum::kICCProfile == csenum) {
- // TODO: write the ICCProfile blob
+ if (fColorSpace) {
+ sk_sp<SkData> data = fColorSpace->serialize();
+ if (data) {
+ buffer.writeDataAsByteArray(data.get());
+ } else {
+ buffer.writeByteArray(nullptr, 0);
+ }
+ } else {
+ sk_sp<SkData> data = SkData::MakeEmpty();
+ buffer.writeDataAsByteArray(data.get());
}
}
diff --git a/src/core/SkMatrix44.cpp b/src/core/SkMatrix44.cpp
index 34b5327..56c2e8a 100644
--- a/src/core/SkMatrix44.cpp
+++ b/src/core/SkMatrix44.cpp
@@ -85,6 +85,17 @@
#endif
}
+void SkMatrix44::as4x3ColMajorf(float dst[]) const {
+ const SkMScalar* src = &fMat[0][0];
+#ifdef SK_MSCALAR_IS_DOUBLE
+ for (int i = 0; i < 12; ++i) {
+ dst[i] = SkMScalarToFloat(src[i]);
+ }
+#elif defined SK_MSCALAR_IS_FLOAT
+ memcpy(dst, src, 12 * sizeof(float));
+#endif
+}
+
void SkMatrix44::asColMajord(double dst[]) const {
const SkMScalar* src = &fMat[0][0];
#ifdef SK_MSCALAR_IS_DOUBLE
@@ -217,6 +228,14 @@
this->dirtyTypeMask();
}
+void SkMatrix44::set4x3ColMajorf(const float src[]) {
+ fMat[0][0] = src[0]; fMat[0][1] = src[1]; fMat[0][2] = src[2]; fMat[0][3] = src[3];
+ fMat[1][0] = src[4]; fMat[1][1] = src[5]; fMat[1][2] = src[6]; fMat[1][3] = src[7];
+ fMat[2][0] = src[8]; fMat[2][1] = src[9]; fMat[2][2] = src[10]; fMat[2][3] = src[11];
+ fMat[3][0] = 0; fMat[3][1] = 0; fMat[3][2] = 0; fMat[3][3] = 1;
+ this->dirtyTypeMask();
+}
+
///////////////////////////////////////////////////////////////////////////////
void SkMatrix44::setTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz) {
diff --git a/tests/ColorSpaceTest.cpp b/tests/ColorSpaceTest.cpp
index 4b39ab0..853bdce 100644
--- a/tests/ColorSpaceTest.cpp
+++ b/tests/ColorSpaceTest.cpp
@@ -165,3 +165,41 @@
SkImageInfo info = SkImageInfo::MakeS32(10, 10, kPremul_SkAlphaType);
REPORTER_ASSERT(r, info.gammaCloseToSRGB());
}
+
+static void test_serialize(skiatest::Reporter* r, SkColorSpace* space, bool isNamed) {
+ sk_sp<SkData> data = space->serialize();
+ sk_sp<SkColorSpace> newSpace = SkColorSpace::Deserialize(data->data(), data->size());
+
+ if (isNamed) {
+ REPORTER_ASSERT(r, space == newSpace.get());
+ } else {
+ REPORTER_ASSERT(r, space->gammaNamed() == newSpace->gammaNamed());
+
+ REPORTER_ASSERT(r, space->xyz().getFloat(0, 0) == newSpace->xyz().getFloat(0, 0));
+ REPORTER_ASSERT(r, space->xyz().getFloat(0, 1) == newSpace->xyz().getFloat(0, 1));
+ REPORTER_ASSERT(r, space->xyz().getFloat(0, 2) == newSpace->xyz().getFloat(0, 2));
+ REPORTER_ASSERT(r, space->xyz().getFloat(0, 3) == newSpace->xyz().getFloat(0, 3));
+ REPORTER_ASSERT(r, space->xyz().getFloat(1, 0) == newSpace->xyz().getFloat(1, 0));
+ REPORTER_ASSERT(r, space->xyz().getFloat(1, 1) == newSpace->xyz().getFloat(1, 1));
+ REPORTER_ASSERT(r, space->xyz().getFloat(1, 2) == newSpace->xyz().getFloat(1, 2));
+ REPORTER_ASSERT(r, space->xyz().getFloat(1, 3) == newSpace->xyz().getFloat(1, 3));
+ REPORTER_ASSERT(r, space->xyz().getFloat(2, 0) == newSpace->xyz().getFloat(2, 0));
+ REPORTER_ASSERT(r, space->xyz().getFloat(2, 1) == newSpace->xyz().getFloat(2, 1));
+ REPORTER_ASSERT(r, space->xyz().getFloat(2, 2) == newSpace->xyz().getFloat(2, 2));
+ REPORTER_ASSERT(r, space->xyz().getFloat(2, 3) == newSpace->xyz().getFloat(2, 3));
+ REPORTER_ASSERT(r, space->xyz().getFloat(3, 0) == newSpace->xyz().getFloat(3, 0));
+ REPORTER_ASSERT(r, space->xyz().getFloat(3, 1) == newSpace->xyz().getFloat(3, 1));
+ REPORTER_ASSERT(r, space->xyz().getFloat(3, 2) == newSpace->xyz().getFloat(3, 2));
+ REPORTER_ASSERT(r, space->xyz().getFloat(3, 3) == newSpace->xyz().getFloat(3, 3));
+ }
+}
+
+DEF_TEST(ColorSpace_Serialize, r) {
+ test_serialize(r, SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named).get(), true);
+ test_serialize(r, SkColorSpace::NewNamed(SkColorSpace::kAdobeRGB_Named).get(), true);
+
+ sk_sp<SkData> monitorData = SkData::MakeFromFileName(
+ GetResourcePath("monitor_profiles/HP_ZR30w.icc").c_str());
+ test_serialize(r, SkColorSpace::NewICC(monitorData->data(), monitorData->size()).get(), false);
+}
+
diff --git a/tests/ImageIsOpaqueTest.cpp b/tests/ImageIsOpaqueTest.cpp
index 42c9f99..843c1d1 100644
--- a/tests/ImageIsOpaqueTest.cpp
+++ b/tests/ImageIsOpaqueTest.cpp
@@ -31,9 +31,7 @@
info2.unflatten(rb);
REPORTER_ASSERT(reporter, rb.offset() == wb.bytesWritten());
- // FIXME (msarett):
- // Support flatten/unflatten of SkColorSpace objects.
- REPORTER_ASSERT(reporter, info.makeColorSpace(nullptr) == info2.makeColorSpace(nullptr));
+ REPORTER_ASSERT(reporter, info == info2);
}
DEF_TEST(ImageInfo_flattening, reporter) {