|
|
Chromium Code Reviews|
Created:
8 years, 9 months ago by Francois Modified:
8 years, 9 months ago CC:
chromium-reviews Base URL:
http://src.chromium.org/svn/trunk/src/ Visibility:
Public. |
DescriptionSupport for interlaced PNGs (in gfx::PNGCodec)
BUG=30339
TEST=Added new unit tests: --gtest_filter=PNGCodec.*. For a manual test, load
up the second test theme attached to the referenced bug (comment #10).
Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=125602
Patch Set 1 #
Total comments: 22
Patch Set 2 : #
Total comments: 19
Patch Set 3 : #Patch Set 4 : #
Total comments: 12
Patch Set 5 : #Patch Set 6 : #
Messages
Total messages: 24 (0 generated)
Hi, please review this patch. I identified reviewers using svn blame, so please reassign to somebody else if I got it wrong. Summary of, and motivation for, changes made by this patch (besides adding support for interlaced PNGs) not covered in the code comments: 1) I removed most of the "converters" which did work for which libpng has built-in support. It was required for the decode side because the manual converters didn't play nice with png_progressive_combine_row, which is required for decoding interlaced images. For the sake of symmetry I also removed most of the encode-side manual converters except for the SkBitmap one because it would need to be modified to be used as a libpng user transform function, while the other encode-side converters could easily be replaced by built-in libpng transforms. 2) I Added user error and warning functions (passed to libpng) which allow us to log libpng errors and warnings through Chrome's logging facilities instead of stderr. 3) I had to make ui_unittests link against libpng because I needed to be able to encode interlaced, grayscale, and palette-based PNGs (for which I added new unit tests), none of which gfx::PNGCodec::Encode supports. Thanks a lot! https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.cc File ui/gfx/codec/png_codec.cc (left): https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.... ui/gfx/codec/png_codec.cc:55: } The work done by this converter and the one above it is now done by libpng itself (e.g. by png_set_strip_alpha and png_set_bgr). https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.... ui/gfx/codec/png_codec.cc:83: } The work done by this converter and the one above it is now done by built-in libpng transforms (png_set_add_alpha to convert from RGB to RGBA) and a single libpng user transform called ConvertRGBARowToSkia. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.... ui/gfx/codec/png_codec.cc:187: bool* is_opaque); All decode converters have been removed in favor of built-in libpng conversions and a single user transform function for decoding to Skia. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.... ui/gfx/codec/png_codec.cc:208: pixel_out[3] = 0xff; This converter's work is now done by libpng itself (png_set_add_alpha). https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.... ui/gfx/codec/png_codec.cc:223: This converter's work is now done by libpng itself (png_set_strip_alpha). https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.... ui/gfx/codec/png_codec.cc:554: } Replaced by a combination of png_set_filler (stips alpha during encode) and png_set_bgr. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.... ui/gfx/codec/png_codec.cc:667: } I merged this function back into EncodeWithCompressionLevel because 1) I needed to set the user transform function in the midst of this function's body, and adding yet another parameter and the logic to check it seemed sub-optimal; 2) neither g++ (a newer version than on Ubuntu 10.04/Lucid, admittedly) nor clang generated warnings due to this change; 3) setjmp was being called by Decode() in the presence of local variables even before my changes were made; 4) all call sites of setjmp look to be fine according to the NOTES section of the longjmp(3) manpage. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.cc File ui/gfx/codec/png_codec.cc (right): https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.... ui/gfx/codec/png_codec.cc:163: } Replaces the converters called ConvertRGBtoSkia and ConvertRGBAtoSkia. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.... ui/gfx/codec/png_codec.cc:293: png_progressive_combine_row(png_ptr, dest, new_row); This call merges the latest changes to the rows of an interlaced PNG with their prior changes. For non-interlaced images it simply does the memcpy as the old code used to if the converter was NULL. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.... ui/gfx/codec/png_codec.cc:609: int input_color_components; This block was merged from what used to be DoLibpngWrite() (and minor changes made). https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.... ui/gfx/codec/png_codec.cc:689: return true; Large parts of this block and the two above it were merged from what used to be DoLibpngWrite(), after which some changes were made. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec_... File ui/gfx/codec/png_codec_unittest.cc (left): https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec_... ui/gfx/codec/png_codec_unittest.cc:194: ASSERT_TRUE(original == decoded); This function was moved further up. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec_... File ui/gfx/codec/png_codec_unittest.cc (right): https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec_... ui/gfx/codec/png_codec_unittest.cc:9: #endif Required in order to encode interlaced, palette-based, and grayscale images (not supported by PNGCodec::Encode). It was necessary to add tests for these types because PNGCodec is actually used to decode such images. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec_... ui/gfx/codec/png_codec_unittest.cc:122: void FlushImageData(png_structp) {} Will change this to void FlushImageData(png_structp /*png_ptr*/) {} when I upload the next patch set. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec_... ui/gfx/codec/png_codec_unittest.cc:352: } Old code moved from further down. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/ui_unittests.gypi File ui/ui_unittests.gypi (right): https://chromiumcodereview.appspot.com/9496004/diff/1/ui/ui_unittests.gypi#ne... ui/ui_unittests.gypi:45: '../third_party/libpng/libpng.gyp:libpng', Required in order to encode interlaced, palette-based, and grayscale images (not supported by PNGCodec::Encode) in png_codec_unittest.cc.
I just glanced at the change, but it looks like some of the changes are using libpng APIs rather than our own code and some of the changes are bug fixes. It would be easier to review if you split this into 2 changes. 1 change that enables and uses libpng APIs and one change for bug fixes. brettw is probably the best reviewer for this. https://chromiumcodereview.appspot.com/9496004/diff/1/third_party/libpng/pngu... File third_party/libpng/pngusr.h (left): https://chromiumcodereview.appspot.com/9496004/diff/1/third_party/libpng/pngu... third_party/libpng/pngusr.h:53: #define PNG_NO_READ_STRIP_ALPHA Does Chromium still compile if you build with use_system_libpng=1 (passed to gyp_chromium). I.e., does the system libpng on Linux normally have these features enabled?
agree with tony about splitting patch up. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.cc File ui/gfx/codec/png_codec.cc (right): https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.... ui/gfx/codec/png_codec.cc:309: template<int IOMode> We don't use templates like this. Even if you have to copypasta the ctor/members, it would be quicker for someone who didn't know the code to see what you're doing if you did this normally instead of w/ partially specialized destructors. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.... ui/gfx/codec/png_codec.cc:352: // errors and warnings using Chrome's logging facilities instead of stderr. Sweet!
On 2012/02/28 18:34:05, tony wrote: > I just glanced at the change, but it looks like some of the changes are using > libpng APIs rather than our own code and some of the changes are bug fixes. It > would be easier to review if you split this into 2 changes. 1 change that > enables and uses libpng APIs and one change for bug fixes. I think it's a good idea to split the patch, but the bug fix depends on some of the changes that switch to the libpng API. One example is that I needed to ditch some of the manual converters in favour of built-in libpng transforms because otherwise png_progressive_combine_row (called in PNGCodec::DecodeRowCallback) wouldn't work. But I'll strip this patch down the the bare minimum required to fix this bug, and then put the rest of the changes in another patch (or patches, if appropriate). > > brettw is probably the best reviewer for this. > > https://chromiumcodereview.appspot.com/9496004/diff/1/third_party/libpng/pngu... > File third_party/libpng/pngusr.h (left): > > https://chromiumcodereview.appspot.com/9496004/diff/1/third_party/libpng/pngu... > third_party/libpng/pngusr.h:53: #define PNG_NO_READ_STRIP_ALPHA > Does Chromium still compile if you build with use_system_libpng=1 (passed to > gyp_chromium). I.e., does the system libpng on Linux normally have these > features enabled? Yes, it does still compile, thankfully.
Hi, I've stripped out everything but the code required for interlaced PNG support (which is what the referenced bug is about). Please review patch set 2. Thanks! https://chromiumcodereview.appspot.com/9496004/diff/1/third_party/libpng/pngu... File third_party/libpng/pngusr.h (left): https://chromiumcodereview.appspot.com/9496004/diff/1/third_party/libpng/pngu... third_party/libpng/pngusr.h:53: #define PNG_NO_READ_STRIP_ALPHA On 2012/02/28 18:34:05, tony wrote: > Does Chromium still compile if you build with use_system_libpng=1 (passed to > gyp_chromium). I.e., does the system libpng on Linux normally have these > features enabled? I can confirm that it does compile with the system libpng. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.cc File ui/gfx/codec/png_codec.cc (right): https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec.... ui/gfx/codec/png_codec.cc:309: template<int IOMode> On 2012/02/28 18:37:43, Elliot Glaysher wrote: > We don't use templates like this. Even if you have to copypasta the > ctor/members, it would be quicker for someone who didn't know the code to see > what you're doing if you did this normally instead of w/ partially specialized > destructors. This has been removed from the current CL. https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec_... File ui/gfx/codec/png_codec_unittest.cc (right): https://chromiumcodereview.appspot.com/9496004/diff/1/ui/gfx/codec/png_codec_... ui/gfx/codec/png_codec_unittest.cc:122: void FlushImageData(png_structp) {} On 2012/02/28 15:00:36, Francois wrote: > Will change this to void FlushImageData(png_structp /*png_ptr*/) {} when I > upload the next patch set. Done. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... File ui/gfx/codec/png_codec.cc (left): https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:81: } The work done by this converter and the one above it is now done by built-in libpng transforms (png_set_add_alpha to convert from RGB to RGBA) and a single libpng user transform called ConvertRGBARowToSkia. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:187: bool* is_opaque); All decode converters have been removed in favor of built-in libpng conversions and a single user transform function for decoding to Skia. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:208: pixel_out[3] = 0xff; This converter's work is now done by libpng itself (png_set_add_alpha). https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:223: This converter's work is now done by libpng itself (png_set_bgr and png_set_add_alpha). https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... File ui/gfx/codec/png_codec.cc (right): https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:187: } This is the libpng user transform which replaces the deleted converters. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:317: png_progressive_combine_row(png_ptr, dest, new_row); This call merges the latest changes to the rows of an interlaced PNG with their prior changes. For non-interlaced images it simply does the memcpy as the old code used to if the converter was NULL. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... File ui/gfx/codec/png_codec_unittest.cc (left): https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec_unittest.cc:194: ASSERT_TRUE(original == decoded); This function was moved further up. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... File ui/gfx/codec/png_codec_unittest.cc (right): https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec_unittest.cc:9: #endif Required in order to encode interlaced, palette-based, and grayscale images (not supported by PNGCodec::Encode). It was necessary to add tests for these types because PNGCodec is actually used to decode such images. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec_unittest.cc:302: } Old code moved from further down.
Brett, can you review this change? https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... File ui/gfx/codec/png_codec.cc (right): https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:300: if (!new_row) return; // Interlaced image; row didn't change this pass. Nit: Put the return on it's own line. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... File ui/gfx/codec/png_codec_unittest.cc (right): https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec_unittest.cc:72: void FlushImageData(png_structp /*png_ptr*/) {} Nit: closing } goes on its own line. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec_unittest.cc:121: if (!png_ptr) return false; Nit: return false on its own line. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec_unittest.cc:161: default: Nit: It's better to not have a default so you get a compiler error when a new enum value is added. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec_unittest.cc:178: png_set_tRNS(png_ptr, Nit: I think this if requires {} because the body is more than one line.
Thanks Tony, your comments have been addressed. I also added a unit test for decoding interlaced BGR (BGRA was already covered). https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... File ui/gfx/codec/png_codec.cc (right): https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:300: if (!new_row) return; // Interlaced image; row didn't change this pass. On 2012/02/29 18:57:57, tony wrote: > Nit: Put the return on it's own line. Done. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... File ui/gfx/codec/png_codec_unittest.cc (right): https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec_unittest.cc:72: void FlushImageData(png_structp /*png_ptr*/) {} On 2012/02/29 18:57:57, tony wrote: > Nit: closing } goes on its own line. Done. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec_unittest.cc:121: if (!png_ptr) return false; On 2012/02/29 18:57:57, tony wrote: > Nit: return false on its own line. Done. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec_unittest.cc:161: default: On 2012/02/29 18:57:57, tony wrote: > Nit: It's better to not have a default so you get a compiler error when a new > enum value is added. Done. https://chromiumcodereview.appspot.com/9496004/diff/11003/ui/gfx/codec/png_co... ui/gfx/codec/png_codec_unittest.cc:178: png_set_tRNS(png_ptr, On 2012/02/29 18:57:57, tony wrote: > Nit: I think this if requires {} because the body is more than one line. Done.
I'm pretty backed up now and I honestly don't have any special ability to review this code. Is there somebody else that can do a detailed look?
Francois, I can try to review this change if you split it into two changes: 1) A refactoring change that just switches to using some libpng API calls. This is easy to review and should be covered by existing tests (or you can add some tests for this). 2) A change that adds support for interlaced pngs. With the refactor out of the way, the diff should be pretty small making it easier for me to review for correctness.
On 2012/03/05 22:22:50, tony wrote: > Francois, I can try to review this change if you split it into two changes: > 1) A refactoring change that just switches to using some libpng API calls. This > is easy to review and should be covered by existing tests (or you can add some > tests for this). > 2) A change that adds support for interlaced pngs. With the refactor out of the > way, the diff should be pretty small making it easier for me to review for > correctness. Hi Tony. I just want to check that we're on the same page. Are you talking about a second, further split? Because, as I said above in my second and third messages, I've already reduced this patch as far as I can in order for it to provide only interlaced PNG support. Are you not satisfied with the amount of reduction I did?
On 2012/03/06 06:40:08, Francois wrote: > Hi Tony. I just want to check that we're on the same page. Are you talking about > a second, further split? Because, as I said above in my second and third > messages, I've already reduced this patch as far as I can in order for it to > provide only interlaced PNG support. Are you not satisfied with the amount of > reduction I did? Right, I'm asking for a second split. It's beneficial to have a refactoring only patch in addition to the patch that fixes the bug.
Hi Tony I have reduced the patch some more. I hope this is enough, because I don't think I can remove anything else. Regards, Francois
https://chromiumcodereview.appspot.com/9496004/diff/22005/ui/gfx/codec/png_co... File ui/gfx/codec/png_codec.cc (right): https://chromiumcodereview.appspot.com/9496004/diff/22005/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:238: png_set_bgr(png_ptr); Why do we call this? Does it replace ConvertRGBtoBGRA usage in the old code? https://chromiumcodereview.appspot.com/9496004/diff/22005/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:259: NOTREACHED() << "Unknown output format"; Please remove the default case. The compiler will notify us if we forgot a case. https://chromiumcodereview.appspot.com/9496004/diff/22005/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:272: state->output_channels = 4; I assume that ConvertBetweenBGRAandRGBA is not needed because of the call to png_set_bgr? https://chromiumcodereview.appspot.com/9496004/diff/22005/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:278: NOTREACHED() << "Unknown output format"; Please remove the default case. The compiler will notify us if we forgot a case. https://chromiumcodereview.appspot.com/9496004/diff/22005/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:303: png_set_user_transform_info(png_ptr, state, 0, 0); Can you make the comment explicit? E.g., png_set_user_transform_info must be called after png_set_strip_alpha (I'm not sure if that's true, but you get the idea). https://chromiumcodereview.appspot.com/9496004/diff/22005/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:342: png_progressive_combine_row(png_ptr, dest, new_row); This is what add interlaced png support, right?
Hi Tony, your comments have been addressed. Thanks! https://chromiumcodereview.appspot.com/9496004/diff/22005/ui/gfx/codec/png_co... File ui/gfx/codec/png_codec.cc (right): https://chromiumcodereview.appspot.com/9496004/diff/22005/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:238: png_set_bgr(png_ptr); On 2012/03/06 19:55:30, tony wrote: > Why do we call this? Does it replace ConvertRGBtoBGRA usage in the old code? That's correct. I've moved this into the switch statement in patch set 5. It's probably cleaner to have it in there, even after the coming refactoring changes. https://chromiumcodereview.appspot.com/9496004/diff/22005/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:259: NOTREACHED() << "Unknown output format"; On 2012/03/06 19:55:30, tony wrote: > Please remove the default case. The compiler will notify us if we forgot a > case. Done. https://chromiumcodereview.appspot.com/9496004/diff/22005/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:272: state->output_channels = 4; On 2012/03/06 19:55:30, tony wrote: > I assume that ConvertBetweenBGRAandRGBA is not needed because of the call to > png_set_bgr? Correct. https://chromiumcodereview.appspot.com/9496004/diff/22005/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:278: NOTREACHED() << "Unknown output format"; On 2012/03/06 19:55:30, tony wrote: > Please remove the default case. The compiler will notify us if we forgot a > case. Done. https://chromiumcodereview.appspot.com/9496004/diff/22005/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:303: png_set_user_transform_info(png_ptr, state, 0, 0); On 2012/03/06 19:55:30, tony wrote: > Can you make the comment explicit? E.g., png_set_user_transform_info must be > called after png_set_strip_alpha (I'm not sure if that's true, but you get the > idea). Done. https://chromiumcodereview.appspot.com/9496004/diff/22005/ui/gfx/codec/png_co... ui/gfx/codec/png_codec.cc:342: png_progressive_combine_row(png_ptr, dest, new_row); On 2012/03/06 19:55:30, tony wrote: > This is what add interlaced png support, right? Yes, this is the core change that was required for interlaced support.
LGTM
On 2012/03/07 18:18:13, tony wrote: > LGTM lgtm + cq
CQ is trying da patch. Follow status at https://chromium-status.appspot.com/cq/francoisk777@gmail.com/9496004/25012
Presubmit check for 9496004-25012 failed and returned exit status 1. Running presubmit commit checks ... ** Presubmit ERRORS ** Missing LGTM from an OWNER for: ui/gfx/codec/png_codec_unittest.cc,ui/gfx/codec/png_codec.cc,ui/ui_unittests.gypi Presubmit checks took 1.3s to calculate.
+sky for owners stamp?
Since Tony reviewed this, I'm rubber stamping it: LGTM
CQ is trying da patch. Follow status at https://chromium-status.appspot.com/cq/francoisk777@gmail.com/9496004/25012
Try job failure for 9496004-25012 (retry) on win_rel for step "compile" (clobber build). It's a second try, previously, step "compile" failed. http://build.chromium.org/p/tryserver.chromium/buildstatus?builder=win_rel&nu...
CQ is trying da patch. Follow status at https://chromium-status.appspot.com/cq/francoisk777@gmail.com/9496004/39001
Change committed as 125602 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
