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

Rounded Corners Pull Request #127

Closed
abhibeckert opened this issue May 25, 2022 · 17 comments
Closed

Rounded Corners Pull Request #127

abhibeckert opened this issue May 25, 2022 · 17 comments

Comments

@abhibeckert
Copy link

abhibeckert commented May 25, 2022

I've written a custom output class to generate rounded corners, like this:

Screen Shot 2022-05-25 at 14 00 15

It works fine for my use case, but would you be interested in a pull request? If you are then I'll clean it up, write some tests, etc.

Also it currently only outputs to SVG. Which is all I need myself, I wouldn't want to write the code to do rounded corners as a bitmap...

@codemasher
Copy link
Member

Wow! That looks great! A PR would be much appreciated! I'll see if it can be at least implemented in the ImageMagick output, GD seems too clunky to draw round corners anyway...

@walidaalhomsi
Copy link

@abhibeckert thanks for sharing your experience, if PR needs a lot of cleaning and tests ... can you share the snippet which creates the rounded corners here?

@abhibeckert
Copy link
Author

So the main challenge I had is SVG doesn't seem to handle shapes that touch each other - it tends to draw hairline cracks in between. I wasn't able to completely eliminate the issue but I was able to get pretty close by overlapping shapes... which is mostly where the code became messy as I was it was largely trial end error with minimal planning.

Here's my test code: https://gist.github.com/abhibeckert/2bfb6a884c7fdd3194af86bd28ef9c10

I will clean it up some time soon as I plan to use it in production.

@codemasher
Copy link
Member

The hairlines happen when the modules are not in the same <path d> element - see #57 - or in your case it's probably the individual <use> elements.

@walidaalhomsi
Copy link

Thanks for sharing your code, it is really helpful.

@codemasher
Copy link
Member

codemasher commented Jun 7, 2022

Hey @abhibeckert, since i had time over the long weekend i did some thinking and played around with your test code and wanted to share some feedback and ideas.

Edit: here's the updated example of yours: https://gist.github.com/codemasher/6309f5159f1008cb988f54dde439a64b

So unfortunately the render issues are a common problem and exist since forever (the oldest Stackoverflow questions on that issue date back to like 2011) and the most common suggestion is to put everything that shall not cause issues in a single path, as I mentioned before. There's the shape-rendering="crispEdges" attribute which technically should prevent this but instead makes the edges even more scruffy.

geometricPrecision
image
crispEdges
image

I do like the <symbol>/<use> syntax as it could make life easy with translate and transform and keep the file size small but aside from the render issue there's another problem with it and I'll just leave this screencap here 🙃
image

So we need to go the hard and ugly way and generate a single path element with all modules (dark and light) shaped accordingly to their neighbour fields. So since it has been asked in #126 before, I came up with a method that returns a bitmask that indicates the status of the 8 fields around the current one, and which I'm willing to add to QRMatrix:

	/**
	 * Checks the status neighboring modules of the given module at ($x, $y) and returns a bitmask with the results.
	 *
	 * The 8 flags of the bitmask represent the status of each of the neighbouring fields,
	 * starting with the lowest bit for top left, going clockwise:
	 *
	 *   1 2 3
	 *   8 # 4
	 *   7 6 5
	 *
	 * @todo: when $M_TYPE_VALUE is given, it should check for the same $M_TYPE while igrnoring the IS_DARK flag
	 *
	 * (this method may be added to QRMatrix as direct array access is faster than method calls)
	 */
	protected function checkNeighbours(int $x, int $y, int $M_TYPE_VALUE):int{
		$bits      = 0;
		// may be added as constant to QRMatrix
		$neighbours = [
			0b00000001 => [-1, -1],
			0b00000010 => [ 0, -1],
			0b00000100 => [ 1, -1],
			0b00001000 => [ 1,  0],
			0b00010000 => [ 1,  1],
			0b00100000 => [ 0,  1],
			0b01000000 => [-1,  1],
			0b10000000 => [-1,  0]
		];

		foreach($neighbours as $bit => $coord){
			if($this->matrix->check($x + $coord[0], $y + $coord[1])){
				$bits |= $bit;
			}
		}

		return $bits;
	}

In addition I made paths (with relative coordinates) for all of the necessary shapes:

shapes

The shapes just need to be parametrized with the common values as numbered arguments ($x, $y, $r, 1 - $r, 1 - 2 * $r) so that they can be fed into sprintf().
What's left is to figure out how to utilize the bitmask most efficient, but I don't have the brain juices for that right now.
Also I'd like to ask you to submit a PR either way so that you're listed as contributor - your example is already valuable and we can figure out the details then. 🙂

cheers!

@codemasher
Copy link
Member

Oh, i forgot to mention that I've added some isset() in QRMatrix to make several sanitiy checks obsolete, among other things.
Check out the latest version:
composer require chillerlan/php-qrcode:dev-main#cde2af576b6b829b1a30e3042eba488c90d94478

@codemasher
Copy link
Member

This is it:

$bits  = $this->checkNeighbours($x, $y, $M_TYPE);
$check = fn(int $all, int $any):bool => ($bits & ($all | (~$any & 0xff))) === $all;

// 1 2 3
// 8 # 4
// 7 6 5

// checking if a rounded corner on 1 (top left) only is valid
// for this, the right (4) and bottom edge (6) must have neighbours, top (2) and left (8) must not
// neighbours on the corners (1,3,5,7) are optional
// $bits = 0b00101101
$check(0b00101000, 0b01010101); // -> true

@codemasher
Copy link
Member

codemasher commented Jul 10, 2022

Ok, so i have implemented the thing, code over here. Turns out it is surprisingly tricky to recreate the effect in an ImageMagick raster image as there seems no easy way to just draw a path and fill it with a color. However, it always possible to convert the SVG to a raster image via the Inkscape command line interface.

The result looks something like this:
svgMeltedModules

I have also added an option to inverse the effect, so that it "flows" along the dark modules:
svgMeltedModules-inverse

@abhibeckert
Copy link
Author

@codemasher there's a bug in your example code.

Specifically this line:

if($this->options->connectPaths && $this->matrix->checkTypeNotIn($x, $y, $this->options->excludeFromConnect)){

Fails because checkTypeNotIn doesn't exist. It needs to be checkTypeIn (and obviously add a ! operator).

@codemasher
Copy link
Member

Hey, i haven't updated the examples here in this thread and on gist, instead i have added the full working example over here: https://github.com/chillerlan/php-qrcode/blob/main/examples/svgMeltedModules.php

@kusiu
Copy link

kusiu commented May 11, 2023

@codemasher the example doesnt seem to be working with latest version.

@codemasher
Copy link
Member

@kusiu it works fine with 5.0-beta or the dev-main branch, it won't work with v4.3.x.

@abhibeckert
Copy link
Author

@codemasher I've added one more tweak:

.dark {stroke: url(#grad); stroke-width: 0.01;}

(replace the gradient with a solid color, if that's what you're using... I normally use a subtle gradient)

The extra stroke fixes the edge issues in this example (note this issue only occurs with some SVG renderers):

image

The slightly fatter dark blocks result in this:

image

@codemasher
Copy link
Member

Hey, thank you for the feedback! I'll check out adding a stroke, but so far i haven't run into the render issue again.
Could you please post the raw SVG of both examples - I'm curious what causes the hairlines in the first one.

@YeftaAW
Copy link

YeftaAW commented May 23, 2024

I think this is only working with dev-main currently.

@codemasher
Copy link
Member

@YeftaAW how so? Please open a new discussion for your issue.

@chillerlan chillerlan locked as resolved and limited conversation to collaborators May 23, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants