Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added resize capability to Image object #476

Merged
merged 12 commits into from
Jul 17, 2015
Merged

Added resize capability to Image object #476

merged 12 commits into from
Jul 17, 2015

Conversation

flippmoke
Copy link
Member

No description provided.

template <typename T>
void operator()(T & im2) const
{
bool demultiply = mapnik::premultiply_alpha(im2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not mutate the image passed in. Instead expect it is premultiplied: #470

…ion to the way that resize operates such that it requires premultiplied images.
@@ -26,6 +27,7 @@
#include <ostream> // for operator<<, basic_ostream
#include <sstream> // for basic_ostringstream, etc
#include <cstdlib>
#include <iostream>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is #include <iostream> left in from debugging and can be removed?

@flippmoke
Copy link
Member Author

Before merging just wanted to post some performance results:

Using the performance comparison tools from https://github.com/lovell/sharp, I created tests for mapnik associated with JPEG. The change to the script is located here: https://github.com/flippmoke/sharp/blob/perf_test/test/bench/perf.js

The results show the following:

jpeg imagemagick-file-file x 4.16 ops/sec ±1.64% (25 runs sampled)
jpeg imagemagick-native-buffer-buffer x 5.02 ops/sec ±4.32% (29 runs sampled)
jpeg mapnik-buffer-file x 15.08 ops/sec ±2.21% (73 runs sampled)
jpeg mapnik-buffer-buffer x 13.38 ops/sec ±4.41% (67 runs sampled)
jpeg mapnik-file-file x 14.64 ops/sec ±1.56% (72 runs sampled)
jpeg mapnik-file-buffer x 14.53 ops/sec ±3.22% (71 runs sampled)
jpeg gm-buffer-file x 3.02 ops/sec ±2.20% (19 runs sampled)
jpeg gm-buffer-buffer x 2.98 ops/sec ±3.28% (19 runs sampled)
jpeg gm-file-file x 3.04 ops/sec ±2.39% (20 runs sampled)
jpeg gm-file-buffer x 3.15 ops/sec ±2.06% (20 runs sampled)
jpeg sharp-buffer-file x 14.69 ops/sec ±1.12% (72 runs sampled)
jpeg sharp-buffer-buffer x 14.83 ops/sec ±1.23% (72 runs sampled)
jpeg sharp-file-file x 14.82 ops/sec ±0.60% (72 runs sampled)
jpeg sharp-stream-stream x 13.88 ops/sec ±1.99% (68 runs sampled)
jpeg sharp-file-buffer x 14.61 ops/sec ±1.77% (71 runs sampled)
jpeg sharp-promise x 14.62 ops/sec ±1.04% (71 runs sampled)
jpeg sharp-sharpen-mild x 12.97 ops/sec ±2.43% (65 runs sampled)
jpeg sharp-sharpen-radius x 7.29 ops/sec ±1.17% (39 runs sampled)
jpeg sharp-blur-mild x 13.17 ops/sec ±2.88% (65 runs sampled)
jpeg sharp-blur-radius x 10.07 ops/sec ±2.03% (52 runs sampled)
jpeg sharp-nearest-neighbour x 15.62 ops/sec ±0.68% (75 runs sampled)
jpeg sharp-bicubic x 13.71 ops/sec ±1.44% (68 runs sampled)
jpeg sharp-nohalo x 5.69 ops/sec ±1.94% (32 runs sampled)
jpeg sharp-locallyBoundedBicubic x 7.85 ops/sec ±1.64% (42 runs sampled)
jpeg sharp-vertexSplitQuadraticBasisSpline x 14.13 ops/sec ±0.83% (69 runs sampled)
jpeg sharp-gamma x 7.24 ops/sec ±2.70% (40 runs sampled)
jpeg sharp-normalise x 8.19 ops/sec ±2.11% (43 runs sampled)
jpeg sharp-greyscale x 14.96 ops/sec ±0.91% (73 runs sampled)
jpeg sharp-greyscale-gamma x 7.13 ops/sec ±2.45% (39 runs sampled)
jpeg sharp-progressive x 12.89 ops/sec ±1.38% (64 runs sampled)
jpeg sharp-without-chroma-subsampling x 13.88 ops/sec ±0.86% (68 runs sampled)
jpeg sharp-rotate x 15.23 ops/sec ±0.80% (74 runs sampled)
jpeg sharp-sequentialRead x 11.35 ops/sec ±1.38% (58 runs sampled)

As you can see the Image.resize method is relatively the same speed as the Sharp implementation. A quick profile of the code showed that out of 2.5 second process sample only a small portion is spent in the resize function (~0.12 seconds). Most of the time is spent in encoding and decoding the image.

screen shot 2015-07-13 at 4 57 12 pm

@springmeyer
Copy link
Member

While travis is now passing, noticing that one appveyor build (x86 with node v0.12.x) appears to be crashing. I see:

√ should resize image down - mitchell

And then the text exits. So it appears that the tests may be crashing (usually an abrupt exit like this is a crash) on the next scaling test which I think is sinc. https://ci.appveyor.com/project/Mapbox/node-mapnik/build/1.0.608/job/4em30rrmj7yplwym#L3225.

We don't have any way on appveyor that I know of to get backtraces like on unix (https://github.com/springmeyer/travis-coredump). /cc @BergWerkGIS in case he has ideas.

@wilhelmberg
Copy link
Contributor

The only thing I can think of at the moment is enabling local dumps:
https://gist.github.com/BergWerkGIS/6385afd9f6e240eeb074#enable-local-dumps

But they would have to be downloaded and analyzed offline.
E.g. zip them and create an artifact.

@wilhelmberg
Copy link
Contributor

Tried to replicate locally (node 0.12.0 and latest source of image_scaling), but this specific test passed.

But another one fails (does too in master):

 463 passing (4s)
  4 pending
  1 failing

  1) Convert to GeoJSON should convert shapefile:

      AssertionError: false == true
      + expected - actual

      -false
      +true

      at Context.<anonymous> (C:\mb\windows-builds-32\packages\node-mapnik\test\shp2geojson.test.js:31:20)

@lovell
Copy link

lovell commented Jul 14, 2015

@flippmoke Some great performance results for mapnik there! Well done. Feel free to submit your benchmark additions back to sharp as a PR when this feature is available.

The near-parity with libvips means you've probably also hit the (de)compression IO limits of libjpeg(-turbo). Do you think Mapbox might consider sponsoring the work required for https://sourceforge.net/p/libjpeg-turbo/feature-requests/34/ to provide a ~30% reduction in JPEG decompression times, at least for AVX2 CPUs? Great P(ublic) R(elations) opportunity :)

@wilhelmberg
Copy link
Contributor

Found the problem should convert shapefile test: Windows vs Linux line endings.

@wilhelmberg
Copy link
Contributor

REMOVED
Stack Trace was wrong because of missing PDBs.

@wilhelmberg
Copy link
Contributor

Couldn't get current mapnik built, so I went back to mapnik 3.0.0 tag to get debug symbols.

STACK TRACE:

0347f130 678b5a20 mapnik_668b0000!agg::image_filter_lut::calculate<agg::image_filter_sinc>(class agg::image_filter_sinc * filter = 0x0347f168, bool normalization = true)+0xec [c:\mb\windows-builds-32\packages\mapnik-v3.0.0\deps\agg\include\agg_image_filters.h @ 66]
0347f1fc 678b5607 mapnik_668b0000!mapnik::detail::set_scaling_method<agg::image_filter_lut>(class agg::image_filter_lut * filter = 0x0347f344, mapnik::scaling_method_e scaling_method = SCALING_SINC (0n14), double filter_factor = 5.5658609241213336e+268)+0x180 [c:\mb\windows-builds-32\packages\mapnik-v3.0.0\include\mapnik\image_scaling_traits.hpp @ 202]
0347f844 6837afda mapnik_668b0000!mapnik::scale_image_agg<mapnik::image<mapnik::rgba8_t> >(class mapnik::image<mapnik::rgba8_t> * target = 0x00d18440, class mapnik::image<mapnik::rgba8_t> * source = 0x00d18330, mapnik::scaling_method_e scaling_method = SCALING_SINC (0n14), double image_ratio_x = 1.3333333333333333, double image_ratio_y = 1.3333333333333333, double x_off_f = 0, double y_off_f = 0, double filter_factor = 5.5658609241213336e+268)+0x557 [c:\mb\windows-builds-32\packages\mapnik-v3.0.0\src\image_scaling.cpp @ 163]
0347f8d0 6837b4c8 mapnik!resize_visitor::operator()(class mapnik::image<mapnik::rgba8_t> * im2 = 0x00d18440)+0x4a [c:\mb\windows-builds-32\packages\node-mapnik\src\mapnik_image.cpp @ 1569]
(Inline) -------- mapnik!mapnik::util::detail::dispatcher<resize_visitor,mapnik::image_any,void,mapnik::image<mapnik::rgba8_t>,mapnik::image<mapnik::gray8_t>,mapnik::image<mapnik::gray8s_t>,mapnik::image<mapnik::gray16_t>,mapnik::image<mapnik::gray16s_t>,mapnik::image<mapnik::gray32_t>,mapnik::image<mapnik::gray32s_t>,mapnik::image<mapnik::gray32f_t>,mapnik::image<mapnik::gray64_t>,mapnik::image<mapnik::gray64s_t>,mapnik::image<mapnik::gray64f_t> >::apply+0x11 [c:\mb\windows-builds-32\packages\mapnik-v3.0.0\mapnik-gyp\mapnik-sdk\include\mapnik\util\variant.hpp @ 318]
(Inline) -------- mapnik!mapnik::util::detail::dispatcher<resize_visitor,mapnik::image_any,void,mapnik::image<mapnik::null_t>,mapnik::image<mapnik::rgba8_t>,mapnik::image<mapnik::gray8_t>,mapnik::image<mapnik::gray8s_t>,mapnik::image<mapnik::gray16_t>,mapnik::image<mapnik::gray16s_t>,mapnik::image<mapnik::gray32_t>,mapnik::image<mapnik::gray32s_t>,mapnik::image<mapnik::gray32f_t>,mapnik::image<mapnik::gray64_t>,mapnik::image<mapnik::gray64s_t>,mapnik::image<mapnik::gray64f_t> >::apply+0x38 [c:\mb\windows-builds-32\packages\mapnik-v3.0.0\mapnik-gyp\mapnik-sdk\include\mapnik\util\variant.hpp @ 322]
(Inline) -------- mapnik!mapnik::util::variant<mapnik::image<mapnik::null_t>,mapnik::image<mapnik::rgba8_t>,mapnik::image<mapnik::gray8_t>,mapnik::image<mapnik::gray8s_t>,mapnik::image<mapnik::gray16_t>,mapnik::image<mapnik::gray16s_t>,mapnik::image<mapnik::gray32_t>,mapnik::image<mapnik::gray32s_t>,mapnik::image<mapnik::gray32f_t>,mapnik::image<mapnik::gray64_t>,mapnik::image<mapnik::gray64s_t>,mapnik::image<mapnik::gray64f_t> >::visit+0x38 [c:\mb\windows-builds-32\packages\mapnik-v3.0.0\mapnik-gyp\mapnik-sdk\include\mapnik\util\variant.hpp @ 772]
(Inline) -------- mapnik!mapnik::util::apply_visitor+0x38 [c:\mb\windows-builds-32\packages\mapnik-v3.0.0\mapnik-gyp\mapnik-sdk\include\mapnik\util\variant.hpp @ 838]
0347f9fc 011bde02 mapnik!Image::EIO_Resize(struct uv_work_s * req = 0x00d186a0)+0x218 [c:\mb\windows-builds-32\packages\node-mapnik\src\mapnik_image.cpp @ 1686]
0347fa14 011bd38f node!worker(void * arg = 0x00000000)+0xe2 [c:\mb\windows-builds-32\packages\node-v0.12.0-win32\deps\uv\src\threadpool.c @ 93]
0347fa30 692f131e node!uv__thread_start(void * arg = 0x00d97a20)+0x2f [c:\mb\windows-builds-32\packages\node-v0.12.0-win32\deps\uv\src\win\thread.c @ 139]
0347fa68 777d919f DESKTOPCRT140!thread_start(void * untyped_parameter = 0x00d180c8)+0x7b [f:\dd\vctools\crt\core_crt\src\desktopcrt\startup\threadex.cpp @ 202]
0347fa74 77bba22b KERNEL32!BaseThreadInitThunk+0xe
0347fab8 77bba201 ntdll!__RtlUserThreadStart+0x20
0347fac8 00000000 ntdll!_RtlUserThreadStart+0x1b

Exception Analysis

FAULTING_IP: 
mapnik_668b0000!agg::image_filter_lut::calculate<agg::image_filter_sinc>+ec [c:\mb\windows-builds-32\packages\mapnik-v3.0.0\deps\agg\include\agg_image_filters.h @ 66]
678ae32c 668b4408fe      mov     ax,word ptr [eax+ecx-2]

EXCEPTION_RECORD:  ffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 678ae32c (mapnik_668b0000!agg::image_filter_lut::calculate<agg::image_filter_sinc>+0x000000ec)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000000
   Parameter[1]: fffffffe
Attempt to read from address fffffffe

CONTEXT:  00000000 -- (.cxr 0x0;r)
eax=00000000 ebx=00d1870c ecx=00000000 edx=80000000 esi=0347f344 edi=00000000
eip=678ae32c esp=0347f120 ebp=00000000 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
mapnik_668b0000!agg::image_filter_lut::calculate<agg::image_filter_sinc>+0xec:
678ae32c 668b4408fe      mov     ax,word ptr [eax+ecx-2]  ds:002b:fffffffe=????

FAULTING_THREAD:  00000bc0
PROCESS_NAME:  node.exe
ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%08lx referenced memory at 0x%08lx. The memory could not be %s.
EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%08lx referenced memory at 0x%08lx. The memory could not be %s.
EXCEPTION_PARAMETER1:  00000000
EXCEPTION_PARAMETER2:  fffffffe
READ_ADDRESS:  fffffffe 
FOLLOWUP_IP: 
mapnik_668b0000!agg::image_filter_lut::calculate<agg::image_filter_sinc>+ec [c:\mb\windows-builds-32\packages\mapnik-v3.0.0\deps\agg\include\agg_image_filters.h @ 66]
678ae32c 668b4408fe      mov     ax,word ptr [eax+ecx-2]
NTGLOBALFLAG:  70
APPLICATION_VERIFIER_FLAGS:  0
APP:  node.exe
ANALYSIS_VERSION: 6.3.9600.17237 (debuggers(dbg).140716-0327) x86fre
BUGCHECK_STR:  APPLICATION_FAULT_INVALID_CLASS_PTR_READ_INVALID_POINTER_READ_BEFORE_WRITE
PRIMARY_PROBLEM_CLASS:  INVALID_CLASS_PTR_READ_BEFORE_WRITE
DEFAULT_BUCKET_ID:  INVALID_CLASS_PTR_READ_BEFORE_WRITE
LAST_CONTROL_TRANSFER:  from 678b5a20 to 678ae32c

STACK_TEXT:  
0347f130 678b5a20 0347f168 00000001 00cc0000 mapnik_668b0000!agg::image_filter_lut::calculate<agg::image_filter_sinc>+0xec
0347f1fc 678b5607 0347f344 0000000e 00d186f4 mapnik_668b0000!mapnik::detail::set_scaling_method<agg::image_filter_lut>+0x180
0347f844 6837afda 00d18440 00d18330 0000000e mapnik_668b0000!mapnik::scale_image_agg<mapnik::image<mapnik::rgba8_t> >+0x557
0347f8d0 6837b4c8 00d18440 28aae367 00000000 mapnik!resize_visitor::operator()+0x4a
0347f9fc 011bde02 00d186a0 00000000 0000019c mapnik!Image::EIO_Resize+0x218
0347fa14 011bd38f 00000000 00d97a20 00d180c8 node!worker+0xe2
0347fa30 692f131e 00d97a20 a8b84556 00000000 node!uv__thread_start+0x2f
0347fa68 777d919f 00d180c8 0347fab8 77bba22b DESKTOPCRT140!thread_start+0x7b
0347fa74 77bba22b 00d180c8 b6126124 00000000 KERNEL32!BaseThreadInitThunk+0xe
0347fab8 77bba201 ffffffff 77baf21a 00000000 ntdll!__RtlUserThreadStart+0x20
0347fac8 00000000 692f12a3 00d180c8 00000000 ntdll!_RtlUserThreadStart+0x1b

STACK_COMMAND:  .cxr 0x0 ; kb
FAULTING_SOURCE_LINE:  c:\mb\windows-builds-32\packages\mapnik-v3.0.0\deps\agg\include\agg_image_filters.h
FAULTING_SOURCE_FILE:  c:\mb\windows-builds-32\packages\mapnik-v3.0.0\deps\agg\include\agg_image_filters.h
FAULTING_SOURCE_LINE_NUMBER:  66
SYMBOL_STACK_INDEX:  0
SYMBOL_NAME:  mapnik!agg::image_filter_lut::calculate<agg::image_filter_sinc>+ec
FOLLOWUP_NAME:  MachineOwner
MODULE_NAME: mapnik_668b0000
IMAGE_NAME:  mapnik.dll
DEBUG_FLR_IMAGE_TIMESTAMP:  55a6282a
FAILURE_BUCKET_ID:  INVALID_CLASS_PTR_READ_BEFORE_WRITE_c0000005_mapnik.dll!agg::image_filter_lut::calculate_agg::image_filter_sinc_
BUCKET_ID:  APPLICATION_FAULT_INVALID_CLASS_PTR_READ_INVALID_POINTER_READ_BEFORE_WRITE_mapnik!agg::image_filter_lut::calculate_agg::image_filter_sinc_+ec
ANALYSIS_SOURCE:  UM
FAILURE_ID_HASH_STRING:  um:invalid_class_ptr_read_before_write_c0000005_mapnik.dll!agg::image_filter_lut::calculate_agg::image_filter_sinc_
FAILURE_ID_HASH:  {4ab3ebcf-a6e3-597d-ef98-8753b42b6eb2}
Followup: MachineOwner

LOCALS @agg::image_filter_lut::calculate:

m_radius seems odd?

image

@wilhelmberg
Copy link
Contributor

Did a full rebuild of everything: deps, mapnik, node and node-mapnik.
Still same exception

@flippmoke
Copy link
Member Author

@BergWerkGIS THANK YOU SO MUCH! That dump pointed me in the right direction and found uninitialized value in cef4100. Should be fixed now!

/cc @springmeyer

@springmeyer
Copy link
Member

🚀 teamwork @BergWerkGIS @flippmoke. @flippmoke can you open a new ticket on running cppcheck every commit on travis?

$ cppcheck -v --std=c++11 --enable=all src/mapnik_image.cpp 
Checking src/mapnik_image.cpp...
[src/mapnik_image.cpp:1554]: (warning) Member variable 'resize_visitor::filter_factor_' is not initialized in the constructor.

Also: I would expected that export CXXFLAGS="-Weffc++" would catch this as well. But oddly I'm not seeing a warning on my machine with:

]$ clang++ -v
Apple LLVM version 6.1.0 (clang-602.0.49) (based on LLVM 3.6.0svn)
Target: x86_64-apple-darwin14.3.0
Thread model: posix

@springmeyer springmeyer added this to the v3.4.1 milestone Jul 15, 2015
@wilhelmberg
Copy link
Contributor

Glad I could help.
Any idea, why this popped up just with 32bit, node 0.12.0 and on AppVeyor.
And why other machines and software versions worked?

@springmeyer
Copy link
Member

Any idea, why this popped up just with 32bit, node 0.12.0 and on AppVeyor.
And why other machines and software versions worked?

Great question. It's hard to know of course without being the compiler author and without inspecting the memory layout of the program in detail. But we do know that an uninitialized value is "undefined" behavior in C/C++. So it is not surprising this problem surfaced. It is curious that it did not impact 64 bit systems, other node versions, or other platforms. It may be that the compilers on those systems chose to initialize the value to something more reasonable. Or it could be that the memory layout was such that the value was still huge but just not so huge to surface a problem. I think this is a clear case where supporting windows and cross platform development in general is a big factor in making the software better for everyone. I was certainly temped to ignore this error, but so glad you all persisted in fixing the bug - which would have maybe impacted everyone.

For more details on undefined behavior see http://blog.regehr.org/archives/213. For more specifics on how uninitialized variables work (or don't) see https://en.wikipedia.org/wiki/Uninitialized_variable. The key point is that undefined behavior like this exists intentionally for performance reasons:

Uninitialized variables are a particular problem in languages such as assembly language, C, and C++, which were designed for systems programming. The development of these languages involved a design philosophy in which conflicts between performance and safety were generally resolved in favor of performance. The programmer was given the burden of being aware of dangerous issues such as uninitialized variables.

@springmeyer
Copy link
Member

tests now passing on appveyor (https://ci.appveyor.com/project/Mapbox/node-mapnik/build/1.0.620) so @flippmoke I think this is ready to merge!

@springmeyer
Copy link
Member

@flippmoke - looks like this will not merge automatically because of conflicts in the changelog. I tend to add to the changelog after pulls are merged to avoid this.


void operator()(mapnik::image_null &) const
{
throw std::runtime_error("Can not resize null images");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can not currently reach this line, add COVERALLS ignoring to section.

@wilhelmberg
Copy link
Contributor

@springmeyer thanks for the explanation about uninitialized variables.
As being used to Visual Studio and C# I never would have thought this to be a possible reason.
VS/C# won't even let you compile if you have uninitialized objects and set treat warnings as errors.

@springmeyer @flippmoke
Visual Studio also has some nice analysis tools (latest source of this branch), including helpful explanation and visualization:

image

I can offer, to either take a look from time to time and ticket the list of warnings, or intro you on how to use the tools.

image

@flippmoke
Copy link
Member Author

@springmeyer revert premultiplied() method and remove premultiplied as a property. This is done so that for 3.4.1 there are no breaking API changes.

@springmeyer
Copy link
Member

@BergWerkGIS - yes, would be great to 1) create a separate ticket on any warnings you see, 2) I can help triage them (for example the one above is harmless)

@flippmoke :+1

flippmoke added a commit that referenced this pull request Jul 17, 2015
Added resize capability to Image object
@flippmoke flippmoke merged commit 3b0a4ea into master Jul 17, 2015
@flippmoke flippmoke deleted the image_scaling branch July 17, 2015 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants