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

Bezier curve drawing tool #1335

Open
gammalogic opened this issue Apr 22, 2024 · 7 comments
Open

Bezier curve drawing tool #1335

gammalogic opened this issue Apr 22, 2024 · 7 comments

Comments

@gammalogic
Copy link

Describe the feature you'd like
Provide a bezier curve drawing tool that can work in GD, similar to the pathCurveToQuadraticBezierAbsolute() and pathCurveToRelative() methods provided by Imagick.

Is your feature request related to a problem? Please describe.
Lack of bezier curve drawing support in GD means that a 3rd party library needs to implement it in some form.

GD is so prevalent on PHP servers that this kind of tool (however basic) would be really useful. I need a bezier curve drawing tool in an application I'm working on at the moment (which uses the GD extension) and I eventually found some code in an old Stack Overflow post that does what I want, but not everyone is going to have the patience or time to find example code and get it working. I think it would add value to II to include support for it. It appears that GD is still in active development and I found a reference to a DrawBezier function in the source code, but support doesn't seem to have been extended to the PHP implementation yet. I have received good results from the code I found, which can draw an accurate cubic bezier curve:

Cubic bezier curve drawn using the GD extension

@olivervogel
Copy link
Member

This sounds like an interesting idea.

Intervention Image is a standardized API for GD and Imagick. The main goal is to make the results of the API independent of the driver used and thus ensure interchangeability.

Of course, this goal is not always 100% achievable. However, I have always done my best to get as close to 100% as possible. The same should happen with this feature.

1. Bezier Curve Drawing with GD

It must first be clarified whether it is possible to draw Bezier curves with GD and if so, what type, as there are different types such as cubic, quadratic, etc.

Have you already gained experience here and can you perhaps even show a code example?

2. Design of Public API

In the second step, a public API should be designed with which this curve drawing can be executed. Of course, this is also dependent on the possibilities and Bezier types that are possible with Imagick.

Unfortunately, I'm not really sure which features need to be taken into account here.

A simple call could look like this:

// draw bezier curve
$image->drawBezierCurve(function (BezierCurveFactory $curve) {
    $curve->color('ff5500');
    $curve->point(10, 10);
    $curve->point(150, 150);
    $curve->point(40, 180);
    $curve->point(60, 100);
});

Although, as I said, I'm not sure whether all the necessary things have been taken into account here. For example, there are also control points for Bezier curves, which also have to be mapped somehow.

Can you say more about this?

3. Implementation

Once these questions 1 and 2 have been answered, it's time for the implementation.

@gammalogic
Copy link
Author

Thank you very much for replying, and for your work on the project!

To briefly respond to your questions:

1. Bezier Curve Drawing with GD

A bezier drawing tool does not appear to be available using the GD extension in PHP. There is a reference to a DrawBezier function in the GD C library source code but I haven't been able to determine if this function actually works; in any case, there does not appear to be an implementation of it in the PHP extension. I don't know who the maintainers of the PHP extension are or how closely they're involved in the development of the C library.

I have two examples of working code (from Stack Overflow posts) that I have adapted to draw cubic bezier curves using the line tool or polygon tool in GD; these examples calculate the curve in different ways mathematically but generate very similar results. If there are no issues with linking to the code on StackOverflow here then I'm happy to do so, although I'm aware that copyright/licensing issues need to be kept in mind and it would obviously be preferable if the calculations can be worked out independently without needing to rely on code from SO or another project. I am not yet at a level where I understand how the calculations work, but it's on my to-do list and will try and write some replacement code once I've studied bezier curves in more detail; if I can work out some code I will be more than happy to share it with this project.

2. Design of Public API

The way the Imagick bezier curve drawing functions work is that (generally) they require the user to manually specify the start position of the drawing operation, coordinates for either one or two control points (depending on the curve type), and the end position of the drawing operation. Here is an example of how Imagick does this:

$draw->pathStart();
$draw->pathMoveToAbsolute($start_x, $start_y);

foreach ($points as $key => $point) {
    $draw->pathCurveToQuadraticBezierAbsolute(
        $point['x1'], // control point
        $point['y1'], // control point
        $point['x'],  // end point
        $point['y']   // end point
    );
}

$draw->pathFinish();

AFAIK GD does not using any plotting scheme like this (whereby you have to move a "cursor" to the correct start position before a drawing operation) so a replacement function for GD would have to manually keep track of where the start point should be after each curve is drawn, which can be done easily enough by updating $start_x and $start_y to whatever the end coordinates were for the last point that was drawn. The interface to this functionality might look something like this:

function draw_bezier_curve(int $start_x, int $start_y, $points)
{
}

There are several bezier curve drawing tools available with Imagick but they can be simplified into two basic types of bezier curves which should cover most use cases. Functions such as ImagickDraw::pathCurveToQuadraticBezierSmoothRelative are more complicated and probably outside the scope of a basic requirement.

3. Implementation

TBC

Final Comments

Feature-wise, GD seems pretty static so this functionality might not make it into the PHP extension any time soon, therefore support in II would be a good way to provide it.

@gammalogic
Copy link
Author

This is a very basic animation that I've put together that shows how the points in a cubic bezier curve can be calculated and drawn with a line tool:

Cubic bezier curve calculation animatin

While researching this I found a very helpful interactive guide that explains how the cubic is split to calculate the curve; to replicate the steps in my animation you just need to set the sliders to halfway to replicate the t (ratio) value of 0.5. The maths involved is fairly simple from a computational perspective (nothing more complex than additions and multiplications) and it took approximately 30 splitting operations to draw the curve shown in my animation. The recursion depth was set to 5 but a higher value would produce better results.

@olivervogel
Copy link
Member

Thanks, that looks very promising. as far as I understand you, it is quite possible to draw acceptable cubic Bezier curves with GD and Imagick. If you want to and are ready, I would very much welcome a PR.

@gammalogic
Copy link
Author

This is a comparison of the output from the Imagick::pathCurveToAbsolute() function and my custom GD function:

Cubic Bezier Curve Comparison with Imagick and GD

Allowing for the difference in quality due to Imagick using anti-aliasing and GD not using anti-aliasing, the output from GD is good for 1 pixel wide, acceptable up to 5 pixels wide, and artifacts start appearing after that point (the right-hand curves are 20 pixels wide). These artifacts are in part due to GD's imageline() function not drawing the start and end caps of the line segments perpendicularly. As an experiment, I updated my custom GD function to over-draw an ellipse with a diameter equivalent to the line segment width at each point and this partly solves the problem:

Cubic Bezier Curve Comparison GD Ellipse plus Line

I'm not sure if the output is good enough for inclusion in II, but given GD's limitations I think the results are certainly acceptable and (personally) I would rather have the tool than not.

@olivervogel
Copy link
Member

I think these are great results! Even more so when you consider that GD doesn't actually include the function.

The differences with anti-aliasing can also be observed with the other existing draw functions. I don't think this is a deal breaker, as GD is often very limited anyway.

As I wrote before, the goal should be a 100% match across drivers. As already noted, this is almost impossible to achieve in reality. However, I always try to get as close as possible and I think this is already done here.

I even think that limiting the line width to a maximum of 5 pixels would be an acceptable option to avoid artifacts. There was a similar problem with the implementation of the text outline function (with GD of course). Again, the stroke width was limited to 10 pixels.

@gammalogic
Copy link
Author

Thanks for your feedback. Your suggestion of limiting the line width is a good compromise. I am still trying to get the calculations finished as I am rewriting them to avoid re-using any of the sample code then, if everything is ok, I will attempt a PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants