OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2012 The Android Open Source Project | 2 * Copyright 2012 The Android Open Source Project |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
| 8 #include "SkMagnifierImageFilter.h" |
| 9 |
8 #include "SkBitmap.h" | 10 #include "SkBitmap.h" |
9 #include "SkMagnifierImageFilter.h" | |
10 #include "SkColorPriv.h" | 11 #include "SkColorPriv.h" |
11 #include "SkDevice.h" | |
12 #include "SkReadBuffer.h" | 12 #include "SkReadBuffer.h" |
| 13 #include "SkSpecialImage.h" |
13 #include "SkWriteBuffer.h" | 14 #include "SkWriteBuffer.h" |
14 #include "SkValidationUtils.h" | 15 #include "SkValidationUtils.h" |
15 | 16 |
16 //////////////////////////////////////////////////////////////////////////////// | 17 //////////////////////////////////////////////////////////////////////////////// |
17 #if SK_SUPPORT_GPU | 18 #if SK_SUPPORT_GPU |
| 19 #include "GrContext.h" |
| 20 #include "GrDrawContext.h" |
18 #include "GrInvariantOutput.h" | 21 #include "GrInvariantOutput.h" |
19 #include "effects/GrSingleTextureEffect.h" | 22 #include "effects/GrSingleTextureEffect.h" |
20 #include "glsl/GrGLSLFragmentProcessor.h" | 23 #include "glsl/GrGLSLFragmentProcessor.h" |
21 #include "glsl/GrGLSLFragmentShaderBuilder.h" | 24 #include "glsl/GrGLSLFragmentShaderBuilder.h" |
22 #include "glsl/GrGLSLProgramDataManager.h" | 25 #include "glsl/GrGLSLProgramDataManager.h" |
23 #include "glsl/GrGLSLUniformHandler.h" | 26 #include "glsl/GrGLSLUniformHandler.h" |
24 | 27 |
25 class GrMagnifierEffect : public GrSingleTextureEffect { | 28 class GrMagnifierEffect : public GrSingleTextureEffect { |
26 | 29 |
27 public: | 30 public: |
28 static GrFragmentProcessor* Create(GrTexture* texture, | 31 static GrFragmentProcessor* Create(GrTexture* texture, |
29 const SkRect& bounds, | 32 const SkRect& bounds, |
30 float xOffset, | 33 float xOffset, |
31 float yOffset, | 34 float yOffset, |
32 float xInvZoom, | 35 float xInvZoom, |
33 float yInvZoom, | 36 float yInvZoom, |
34 float xInvInset, | 37 float xInvInset, |
35 float yInvInset) { | 38 float yInvInset) { |
36 return new GrMagnifierEffect(texture, bounds, xOffset, yOffset, xInvZoom
, yInvZoom, xInvInset, | 39 return new GrMagnifierEffect(texture, bounds, |
37 yInvInset); | 40 xOffset, yOffset, |
| 41 xInvZoom, yInvZoom, |
| 42 xInvInset, yInvInset); |
38 } | 43 } |
39 | 44 |
40 virtual ~GrMagnifierEffect() {}; | 45 ~GrMagnifierEffect() override {}; |
41 | 46 |
42 const char* name() const override { return "Magnifier"; } | 47 const char* name() const override { return "Magnifier"; } |
43 | 48 |
44 const SkRect& bounds() const { return fBounds; } // Bounds of source imag
e. | 49 const SkRect& bounds() const { return fBounds; } // Bounds of source imag
e. |
45 // Offset to apply to zoomed pixels, (srcRect position / texture size). | 50 // Offset to apply to zoomed pixels, (srcRect position / texture size). |
46 float x_offset() const { return fXOffset; } | 51 float xOffset() const { return fXOffset; } |
47 float y_offset() const { return fYOffset; } | 52 float yOffset() const { return fYOffset; } |
48 | 53 |
49 // Scale to apply to zoomed pixels (srcRect size / bounds size). | 54 // Scale to apply to zoomed pixels (srcRect size / bounds size). |
50 float x_inv_zoom() const { return fXInvZoom; } | 55 float xInvZoom() const { return fXInvZoom; } |
51 float y_inv_zoom() const { return fYInvZoom; } | 56 float yInvZoom() const { return fYInvZoom; } |
52 | 57 |
53 // 1/radius over which to transition from unzoomed to zoomed pixels (bounds
size / inset). | 58 // 1/radius over which to transition from unzoomed to zoomed pixels (bounds
size / inset). |
54 float x_inv_inset() const { return fXInvInset; } | 59 float xInvInset() const { return fXInvInset; } |
55 float y_inv_inset() const { return fYInvInset; } | 60 float yInvInset() const { return fYInvInset; } |
56 | 61 |
57 private: | 62 private: |
58 GrMagnifierEffect(GrTexture* texture, | 63 GrMagnifierEffect(GrTexture* texture, |
59 const SkRect& bounds, | 64 const SkRect& bounds, |
60 float xOffset, | 65 float xOffset, |
61 float yOffset, | 66 float yOffset, |
62 float xInvZoom, | 67 float xInvZoom, |
63 float yInvZoom, | 68 float yInvZoom, |
64 float xInvInset, | 69 float xInvInset, |
65 float yInvInset) | 70 float yInvInset) |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
160 | 165 |
161 fragBuilder->codeAppendf("\t\t%s = output_color;", args.fOutputColor); | 166 fragBuilder->codeAppendf("\t\t%s = output_color;", args.fOutputColor); |
162 SkString modulate; | 167 SkString modulate; |
163 GrGLSLMulVarBy4f(&modulate, args.fOutputColor, args.fInputColor); | 168 GrGLSLMulVarBy4f(&modulate, args.fOutputColor, args.fInputColor); |
164 fragBuilder->codeAppend(modulate.c_str()); | 169 fragBuilder->codeAppend(modulate.c_str()); |
165 } | 170 } |
166 | 171 |
167 void GrGLMagnifierEffect::onSetData(const GrGLSLProgramDataManager& pdman, | 172 void GrGLMagnifierEffect::onSetData(const GrGLSLProgramDataManager& pdman, |
168 const GrProcessor& effect) { | 173 const GrProcessor& effect) { |
169 const GrMagnifierEffect& zoom = effect.cast<GrMagnifierEffect>(); | 174 const GrMagnifierEffect& zoom = effect.cast<GrMagnifierEffect>(); |
170 pdman.set2f(fOffsetVar, zoom.x_offset(), zoom.y_offset()); | 175 pdman.set2f(fOffsetVar, zoom.xOffset(), zoom.yOffset()); |
171 pdman.set2f(fInvZoomVar, zoom.x_inv_zoom(), zoom.y_inv_zoom()); | 176 pdman.set2f(fInvZoomVar, zoom.xInvZoom(), zoom.yInvZoom()); |
172 pdman.set2f(fInvInsetVar, zoom.x_inv_inset(), zoom.y_inv_inset()); | 177 pdman.set2f(fInvInsetVar, zoom.xInvInset(), zoom.yInvInset()); |
173 pdman.set4f(fBoundsVar, zoom.bounds().x(), zoom.bounds().y(), | 178 pdman.set4f(fBoundsVar, zoom.bounds().x(), zoom.bounds().y(), |
174 zoom.bounds().width(), zoom.bounds().height()); | 179 zoom.bounds().width(), zoom.bounds().height()); |
175 } | 180 } |
176 | 181 |
177 ///////////////////////////////////////////////////////////////////// | 182 ///////////////////////////////////////////////////////////////////// |
178 | 183 |
179 void GrMagnifierEffect::onGetGLSLProcessorKey(const GrGLSLCaps& caps, | 184 void GrMagnifierEffect::onGetGLSLProcessorKey(const GrGLSLCaps& caps, |
180 GrProcessorKeyBuilder* b) const { | 185 GrProcessorKeyBuilder* b) const { |
181 GrGLMagnifierEffect::GenKey(*this, caps, b); | 186 GrGLMagnifierEffect::GenKey(*this, caps, b); |
182 } | 187 } |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
226 | 231 |
227 void GrMagnifierEffect::onComputeInvariantOutput(GrInvariantOutput* inout) const
{ | 232 void GrMagnifierEffect::onComputeInvariantOutput(GrInvariantOutput* inout) const
{ |
228 this->updateInvariantOutputForModulation(inout); | 233 this->updateInvariantOutputForModulation(inout); |
229 } | 234 } |
230 | 235 |
231 #endif | 236 #endif |
232 | 237 |
233 //////////////////////////////////////////////////////////////////////////////// | 238 //////////////////////////////////////////////////////////////////////////////// |
234 | 239 |
235 sk_sp<SkImageFilter> SkMagnifierImageFilter::Make(const SkRect& srcRect, SkScala
r inset, | 240 sk_sp<SkImageFilter> SkMagnifierImageFilter::Make(const SkRect& srcRect, SkScala
r inset, |
236 sk_sp<SkImageFilter> input) { | 241 sk_sp<SkImageFilter> input, |
| 242 const CropRect* cropRect) { |
237 | 243 |
238 if (!SkScalarIsFinite(inset) || !SkIsValidRect(srcRect)) { | 244 if (!SkScalarIsFinite(inset) || !SkIsValidRect(srcRect)) { |
239 return nullptr; | 245 return nullptr; |
240 } | 246 } |
241 // Negative numbers in src rect are not supported | 247 // Negative numbers in src rect are not supported |
242 if (srcRect.fLeft < 0 || srcRect.fTop < 0) { | 248 if (srcRect.fLeft < 0 || srcRect.fTop < 0) { |
243 return nullptr; | 249 return nullptr; |
244 } | 250 } |
245 return sk_sp<SkImageFilter>(new SkMagnifierImageFilter(srcRect, inset, std::
move(input))); | 251 return sk_sp<SkImageFilter>(new SkMagnifierImageFilter(srcRect, inset, |
| 252 std::move(input), |
| 253 cropRect)); |
246 } | 254 } |
247 | 255 |
248 | 256 |
249 SkMagnifierImageFilter::SkMagnifierImageFilter(const SkRect& srcRect, SkScalar i
nset, | 257 SkMagnifierImageFilter::SkMagnifierImageFilter(const SkRect& srcRect, |
250 sk_sp<SkImageFilter> input) | 258 SkScalar inset, |
251 : INHERITED(&input, 1, nullptr) | 259 sk_sp<SkImageFilter> input, |
| 260 const CropRect* cropRect) |
| 261 : INHERITED(&input, 1, cropRect) |
252 , fSrcRect(srcRect) | 262 , fSrcRect(srcRect) |
253 , fInset(inset) { | 263 , fInset(inset) { |
254 SkASSERT(srcRect.x() >= 0 && srcRect.y() >= 0 && inset >= 0); | 264 SkASSERT(srcRect.x() >= 0 && srcRect.y() >= 0 && inset >= 0); |
255 } | 265 } |
256 | 266 |
257 #if SK_SUPPORT_GPU | |
258 bool SkMagnifierImageFilter::asFragmentProcessor(GrFragmentProcessor** fp, | |
259 GrTexture* texture, const SkMat
rix&, | |
260 const SkIRect&bounds) const { | |
261 if (fp) { | |
262 SkScalar yOffset = texture->origin() == kTopLeft_GrSurfaceOrigin ? fSrcR
ect.y() : | |
263 texture->height() - fSrcRect.height() * texture->height() / bounds.he
ight() | |
264 - fSrcRect.y(); | |
265 int boundsY = (texture->origin() == kTopLeft_GrSurfaceOrigin) ? bounds.y
() : | |
266 (texture->height() - bounds.height()); | |
267 SkRect effectBounds = SkRect::MakeXYWH( | |
268 SkIntToScalar(bounds.x()) / texture->width(), | |
269 SkIntToScalar(boundsY) / texture->height(), | |
270 SkIntToScalar(texture->width()) / bounds.width(), | |
271 SkIntToScalar(texture->height()) / bounds.height()); | |
272 SkScalar invInset = fInset > 0 ? SkScalarInvert(fInset) : SK_Scalar1; | |
273 *fp = GrMagnifierEffect::Create(texture, | |
274 effectBounds, | |
275 fSrcRect.x() / texture->width(), | |
276 yOffset / texture->height(), | |
277 fSrcRect.width() / bounds.width(), | |
278 fSrcRect.height() / bounds.height(), | |
279 bounds.width() * invInset, | |
280 bounds.height() * invInset); | |
281 } | |
282 return true; | |
283 } | |
284 #endif | |
285 | |
286 sk_sp<SkFlattenable> SkMagnifierImageFilter::CreateProc(SkReadBuffer& buffer) { | 267 sk_sp<SkFlattenable> SkMagnifierImageFilter::CreateProc(SkReadBuffer& buffer) { |
287 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); | 268 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); |
288 SkRect src; | 269 SkRect src; |
289 buffer.readRect(&src); | 270 buffer.readRect(&src); |
290 return Make(src, buffer.readScalar(), common.getInput(0)); | 271 return Make(src, buffer.readScalar(), common.getInput(0), &common.cropRect()
); |
291 } | 272 } |
292 | 273 |
293 void SkMagnifierImageFilter::flatten(SkWriteBuffer& buffer) const { | 274 void SkMagnifierImageFilter::flatten(SkWriteBuffer& buffer) const { |
294 this->INHERITED::flatten(buffer); | 275 this->INHERITED::flatten(buffer); |
295 buffer.writeRect(fSrcRect); | 276 buffer.writeRect(fSrcRect); |
296 buffer.writeScalar(fInset); | 277 buffer.writeScalar(fInset); |
297 } | 278 } |
298 | 279 |
299 bool SkMagnifierImageFilter::onFilterImageDeprecated(Proxy* proxy, const SkBitma
p& src, | 280 sk_sp<SkSpecialImage> SkMagnifierImageFilter::onFilterImage(SkSpecialImage* sour
ce, |
300 const Context&, SkBitmap* d
st, | 281 const Context& ctx, |
301 SkIPoint* offset) const { | 282 SkIPoint* offset) co
nst { |
302 if ((src.colorType() != kN32_SkColorType) || | 283 SkIPoint inputOffset = SkIPoint::Make(0, 0); |
303 (fSrcRect.width() >= src.width()) || | 284 sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset))
; |
304 (fSrcRect.height() >= src.height())) { | 285 if (!input) { |
305 return false; | 286 return nullptr; |
306 } | 287 } |
307 | 288 |
308 SkAutoLockPixels alp(src); | 289 const SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y
(), |
309 SkASSERT(src.getPixels()); | 290 input->width(), input->height(
)); |
310 if (!src.getPixels() || src.width() <= 0 || src.height() <= 0) { | 291 |
311 return false; | 292 SkIRect bounds; |
| 293 if (!this->applyCropRect(ctx, inputBounds, &bounds)) { |
| 294 return nullptr; |
312 } | 295 } |
313 | 296 |
314 SkAutoTUnref<SkBaseDevice> device(proxy->createDevice(src.width(), src.heigh
t())); | 297 SkScalar invInset = fInset > 0 ? SkScalarInvert(fInset) : SK_Scalar1; |
315 if (!device) { | 298 |
316 return false; | 299 SkScalar invXZoom = fSrcRect.width() / bounds.width(); |
| 300 SkScalar invYZoom = fSrcRect.height() / bounds.height(); |
| 301 |
| 302 |
| 303 #if SK_SUPPORT_GPU |
| 304 if (source->isTextureBacked()) { |
| 305 GrContext* context = source->getContext(); |
| 306 |
| 307 sk_sp<GrTexture> inputTexture(input->asTextureRef(context)); |
| 308 SkASSERT(inputTexture); |
| 309 |
| 310 offset->fX = bounds.left(); |
| 311 offset->fY = bounds.top(); |
| 312 bounds.offset(-inputOffset); |
| 313 |
| 314 SkScalar yOffset = inputTexture->origin() == kTopLeft_GrSurfaceOrigin |
| 315 ? fSrcRect.y() |
| 316 : inputTexture->height() - |
| 317 fSrcRect.height() * inputTexture->height() / bounds.height
() - fSrcRect.y(); |
| 318 int boundsY = inputTexture->origin() == kTopLeft_GrSurfaceOrigin |
| 319 ? bounds.y() |
| 320 : inputTexture->height() - bounds.height(); |
| 321 SkRect effectBounds = SkRect::MakeXYWH( |
| 322 SkIntToScalar(bounds.x()) / inputTexture->width(), |
| 323 SkIntToScalar(boundsY) / inputTexture->height(), |
| 324 SkIntToScalar(inputTexture->width()) / bounds.width(), |
| 325 SkIntToScalar(inputTexture->height()) / bounds.height()); |
| 326 // SRGBTODO: Handle sRGB here |
| 327 sk_sp<GrFragmentProcessor> fp(GrMagnifierEffect::Create( |
| 328 inputTexture.get(), |
| 329 effectBounds, |
| 330 fSrcRect.x() / inputText
ure->width(), |
| 331 yOffset / inputTexture->
height(), |
| 332 invXZoom, |
| 333 invYZoom, |
| 334 bounds.width() * invInse
t, |
| 335 bounds.height() * invIns
et)); |
| 336 if (!fp) { |
| 337 return nullptr; |
| 338 } |
| 339 |
| 340 return DrawWithFP(context, std::move(fp), bounds, source->internal_getPr
oxy()); |
317 } | 341 } |
318 *dst = device->accessBitmap(false); | 342 #endif |
319 SkAutoLockPixels alp_dst(*dst); | |
320 | 343 |
321 SkScalar inv_inset = fInset > 0 ? SkScalarInvert(fInset) : SK_Scalar1; | 344 SkBitmap inputBM; |
322 | 345 |
323 SkScalar inv_x_zoom = fSrcRect.width() / src.width(); | 346 if (!input->getROPixels(&inputBM)) { |
324 SkScalar inv_y_zoom = fSrcRect.height() / src.height(); | 347 return nullptr; |
| 348 } |
325 | 349 |
326 SkColor* sptr = src.getAddr32(0, 0); | 350 if ((inputBM.colorType() != kN32_SkColorType) || |
327 SkColor* dptr = dst->getAddr32(0, 0); | 351 (fSrcRect.width() >= inputBM.width()) || (fSrcRect.height() >= inputBM.h
eight())) { |
328 int width = src.width(), height = src.height(); | 352 return nullptr; |
329 for (int y = 0; y < height; ++y) { | 353 } |
330 for (int x = 0; x < width; ++x) { | 354 |
331 SkScalar x_dist = SkMin32(x, width - x - 1) * inv_inset; | 355 SkAutoLockPixels alp(inputBM); |
332 SkScalar y_dist = SkMin32(y, height - y - 1) * inv_inset; | 356 SkASSERT(inputBM.getPixels()); |
| 357 if (!inputBM.getPixels() || inputBM.width() <= 0 || inputBM.height() <= 0) { |
| 358 return nullptr; |
| 359 } |
| 360 |
| 361 const SkImageInfo info = SkImageInfo::MakeN32Premul(bounds.width(), bounds.h
eight()); |
| 362 |
| 363 SkBitmap dst; |
| 364 if (!dst.tryAllocPixels(info)) { |
| 365 return nullptr; |
| 366 } |
| 367 |
| 368 SkAutoLockPixels dstLock(dst); |
| 369 |
| 370 SkColor* dptr = dst.getAddr32(0, 0); |
| 371 int dstWidth = dst.width(), dstHeight = dst.height(); |
| 372 for (int y = 0; y < dstHeight; ++y) { |
| 373 for (int x = 0; x < dstWidth; ++x) { |
| 374 SkScalar x_dist = SkMin32(x, dstWidth - x - 1) * invInset; |
| 375 SkScalar y_dist = SkMin32(y, dstHeight - y - 1) * invInset; |
333 SkScalar weight = 0; | 376 SkScalar weight = 0; |
334 | 377 |
335 static const SkScalar kScalar2 = SkScalar(2); | 378 static const SkScalar kScalar2 = SkScalar(2); |
336 | 379 |
337 // To create a smooth curve at the corners, we need to work on | 380 // To create a smooth curve at the corners, we need to work on |
338 // a square twice the size of the inset. | 381 // a square twice the size of the inset. |
339 if (x_dist < kScalar2 && y_dist < kScalar2) { | 382 if (x_dist < kScalar2 && y_dist < kScalar2) { |
340 x_dist = kScalar2 - x_dist; | 383 x_dist = kScalar2 - x_dist; |
341 y_dist = kScalar2 - y_dist; | 384 y_dist = kScalar2 - y_dist; |
342 | 385 |
343 SkScalar dist = SkScalarSqrt(SkScalarSquare(x_dist) + | 386 SkScalar dist = SkScalarSqrt(SkScalarSquare(x_dist) + |
344 SkScalarSquare(y_dist)); | 387 SkScalarSquare(y_dist)); |
345 dist = SkMaxScalar(kScalar2 - dist, 0); | 388 dist = SkMaxScalar(kScalar2 - dist, 0); |
346 weight = SkMinScalar(SkScalarSquare(dist), SK_Scalar1); | 389 weight = SkMinScalar(SkScalarSquare(dist), SK_Scalar1); |
347 } else { | 390 } else { |
348 SkScalar sqDist = SkMinScalar(SkScalarSquare(x_dist), | 391 SkScalar sqDist = SkMinScalar(SkScalarSquare(x_dist), |
349 SkScalarSquare(y_dist)); | 392 SkScalarSquare(y_dist)); |
350 weight = SkMinScalar(sqDist, SK_Scalar1); | 393 weight = SkMinScalar(sqDist, SK_Scalar1); |
351 } | 394 } |
352 | 395 |
353 SkScalar x_interp = SkScalarMul(weight, (fSrcRect.x() + x * inv_x_zo
om)) + | 396 SkScalar x_interp = SkScalarMul(weight, (fSrcRect.x() + x * invXZoom
)) + |
354 (SK_Scalar1 - weight) * x; | 397 (SK_Scalar1 - weight) * x; |
355 SkScalar y_interp = SkScalarMul(weight, (fSrcRect.y() + y * inv_y_zo
om)) + | 398 SkScalar y_interp = SkScalarMul(weight, (fSrcRect.y() + y * invYZoom
)) + |
356 (SK_Scalar1 - weight) * y; | 399 (SK_Scalar1 - weight) * y; |
357 | 400 |
358 int x_val = SkTPin(SkScalarFloorToInt(x_interp), 0, width - 1); | 401 int x_val = SkTPin(bounds.x() + SkScalarFloorToInt(x_interp), 0, inp
utBM.width() - 1); |
359 int y_val = SkTPin(SkScalarFloorToInt(y_interp), 0, height - 1); | 402 int y_val = SkTPin(bounds.y() + SkScalarFloorToInt(y_interp), 0, inp
utBM.height() - 1); |
360 | 403 |
361 *dptr = sptr[y_val * width + x_val]; | 404 *dptr = *inputBM.getAddr32(x_val, y_val); |
362 dptr++; | 405 dptr++; |
363 } | 406 } |
364 } | 407 } |
365 return true; | 408 |
| 409 offset->fX = bounds.left(); |
| 410 offset->fY = bounds.top(); |
| 411 return SkSpecialImage::MakeFromRaster(source->internal_getProxy(), |
| 412 SkIRect::MakeWH(bounds.width(), bounds
.height()), |
| 413 dst); |
366 } | 414 } |
367 | 415 |
368 #ifndef SK_IGNORE_TO_STRING | 416 #ifndef SK_IGNORE_TO_STRING |
369 void SkMagnifierImageFilter::toString(SkString* str) const { | 417 void SkMagnifierImageFilter::toString(SkString* str) const { |
370 str->appendf("SkMagnifierImageFilter: ("); | 418 str->appendf("SkMagnifierImageFilter: ("); |
371 str->appendf("src: (%f,%f,%f,%f) ", | 419 str->appendf("src: (%f,%f,%f,%f) ", |
372 fSrcRect.fLeft, fSrcRect.fTop, fSrcRect.fRight, fSrcRect.fBotto
m); | 420 fSrcRect.fLeft, fSrcRect.fTop, fSrcRect.fRight, fSrcRect.fBotto
m); |
373 str->appendf("inset: %f", fInset); | 421 str->appendf("inset: %f", fInset); |
374 str->append(")"); | 422 str->append(")"); |
375 } | 423 } |
376 #endif | 424 #endif |
OLD | NEW |