📜 ⬆️ ⬇️

A tale about how a good young man fought a three-headed snake, or How to embed SVG graphics in Adobe InDesign documents - part two

Continued here this post , authored by viklequick .

Step Five, or Apply the Drunken Master at Work Style
Now, since we already have primitives, let's apply colors, alpha channel and line styles.

  private void applyStroke (Shape shape) {
   Stroke stroke = getStroke ();
   Transform t = getTransform ();
   if (stroke! = null) {
     if (stroke instanceof BasicStroke) {
       BasicStroke currStroke = (BasicStroke) stroke;
       float [] ff = currStroke.getDashArray ();
       if (ff! = null) setdash (ff.length, ff, currStroke.getDashPhase ());
       float width = (float) (currStroke.getLineWidth () * Math.max (t.getScaleX (), t.getScaleY ()));
       if (width <0.7f) width = 0.7f;
       setlinewidth (width);
     }
     else {
       setlinewidth (1.0f);
       Shape strokedShape = stroke.createStrokedShape (shape);
       fill (strokedShape);
     }
   }
   else 
     setlinewidth (1.0f);
 }
Go
image The code in general does not contain anything complicated, just a parsing of BasicStroke. If it is something else, then it can be represented in the form of a Shape and can be drawn like this.
Next, we get the alpha channel level for subsequent shapes, and pass it to InDesign - the code is also quite simple.
')
  private void applyAlpha () {
   float alpha = 1.0f;
   Composite comp = getComposite ();
   if (comp! = null && comp instanceof AlphaComposite) {
     AlphaComposite alcomp = (AlphaComposite) comp;
     alpha = alcomp.getAlpha ();
   }
   setopacity (alpha, true);
 } 
Go
But with custom paints and composites the situation is much sadder. You have to rasterize them yourself.

  private boolean applyStyles (Shape shape) {
   Paint paint = getPaint ();
   if (paint instanceof Color) {
     Color cc = (Color) paint;
     setrgbcolor (cc.getRed () / 255.0f, cc.getGreen () / 255.0f, cc.getBlue () / 255.0f);
     if (cc.getAlpha ()! = 0 && cc.getAlpha ()! = 255) setopacity (cc.getAlpha () / 255.0f, true);
     else applyAlpha ();
   }
   else if (paint! = null && shape! = null) {
     applyAlpha ();
     drawGradientAsBackground (shape, paint);
     return false;
   }

   Composite comp = getComposite ();
   if (comp! = null &&! (comp instanceof AlphaComposite) && shape! = null) {
     drawCustomComposite (shape, comp);
     return false;
   }
   return true;
 } 
Go
The situation with color is fairly obvious (unless the color can be clearly defined alpha channel), but everything else requires explanation. Note that the function returns true / false. True means that no tricks have been applied, and the current shape must be drawn through fill (). But false means that shape is already rasterized as a picture and you don’t need to draw it over the picture.
The gradient itself had to be drawn like this:

  private void drawGradientAsBackground (Shape shape, Paint paint) {
   GraphicsConfiguration conf = getDeviceConfiguration ();
   PaintContext pctx = paint.createContext (
     conf.getColorModel (), conf.getBounds (), shape.getBounds2D (),
     getTransform (), getRenderingHints ());

   Rectangle r = getTransform (). CreateTransformedShape (shape) .getBounds ();
   Raster raster = pctx.getRaster (rx, ry, r.width, r.height);
   ColorModel cmodel = pctx.getColorModel ();
   BufferedImage bfi = new BufferedImage (cmodel, raster.createCompatibleWritableRaster (),
     cmodel.isAlphaPremultiplied (), null);
   bfi.setData (raster);
  
   int [] argbBuf = getBuf (bfi, r.width, r.height);
   if (argbBuf! = null) {
     applyClip (shape);
     drawImage (argbBuf, rx, ry, r.width, r.height, new AffineTransform ());
     restoreClip ();
   }
   pctx.dispose ();
 } 
Go
In this function, the gradient is drawn on an offscreen image, and the image clipping is the shape itself. Similarly, we draw and non-standard Composite:

  private void drawCustomComposite (Shape shape, Composite comp) {
   Rectangle r = getTransform (). CreateTransformedShape (shape) .getBounds ();
   BufferedImage bfi = new BufferedImage (r.width, r.height, BufferedImage.TYPE_INT_ARGB);
   Graphics2D g2 = (Graphics2D) bfi.getGraphics ();
   g2.setTransform (getTransform ());
   g2.setComposite (comp);
   g2.fill (shape);
   g2.dispose ();
  
   int [] argbBuf = getBuf (bfi, r.width, r.height);
   if (argbBuf! = null) {
     applyClip (shape);
     drawImage (argbBuf, rx, ry, r.width, r.height, new AffineTransform ());
     restoreClip ();
   }
 } 
Go
Since we draw an already transformed shape, we set an empty AffineTransform in drawImage.

Step Six, or achieve the foe
image Finally we got to the most interesting. If for other cases the native implementation is essentially a single-line code, in the case of pictures this is not the case. First, here’s the code for drawing the image:

  public boolean drawImage (Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) {
   int [] argbBuf = getBuf (img, width, height);
   if (argbBuf == null) return false;
    
   applyClip (getClip ());
   applyAlpha ();
   drawImage (argbBuf, x, y, width, height, getTransform ());
   restoreClip ();
   return true;
 } 
Go
The code is very simple - sets the clipping, alpha channel, gets the bit matrix - and passes it all to InDesign. Obtaining bits is obvious:

  private int [] getBuf (Image img, int width, int height) {
   int [] argbBuf = new int [width * height];
   PixelGrabber pg = new PixelGrabber (img, 0, 0, width, height, argbBuf, 0, width);
   try {
     if (! pg.grabPixels ()) return null;
   }
   catch (InterruptedException e) {return null;  }
   if ((pg.getStatus () & ImageObserver.ABORT)! = 0) return null;
   return argbBuf;
 } 
Go
But the transfer to InDesign has some features. Consider them.

  private void drawImage (int [] argbBuf, int x, int y, int width, int height, AffineTransform transform) {
   double [] transMatr = new double [6];
   transform.getMatrix (transMatr);
   this.image (argbBuf, x, y, width, height, transMatr);
 } 
Go
It is easy to notice, this is the only function that transforms the transformation matrix to InDesign, and does not work out on its own. It would be possible to go a different way, and always transfer the transformation matrix to Indesign, and not work with transformed shapes. But, to my regret, the inability to draw gradients by means of Indizayn led to this decision.
Now back to C ++ and consider the nuances of the plugin. There is a lot of code here, so I will cite only the most interesting excerpts, removing trivialities and error handling.
The beginning is obvious:

  JNIEXPORT void JNICALL Java _..._ image
   (JNIEnv * env, jobject obj, 
   jintArray imageARGBBuff, jint x, jint y, jint width, jint height,
   jdoubleArray transformMatrix) {

   K2 :: scoped_array <uint8> maskBuffer (new uint8 [imageARGBBuff.length]);
   K2 :: scoped_array <uint8> pictBuffer (new uint8 [3 * imageARGBBuff.length]);
Go
// fill buffers in our ARGB

But then begin dancing with a tambourine.
The thing is - if we have blending color space = RGB, then Indizayn wants a picture in CMYK. Otherwise, it falls down once, causing a terrible spiritual wound to the bogatyr, so much so that this bogatyr cannot even eat. And vice versa - if blending color space = CMYK, then the picture must be in RGB. It was found by experience, and why everything happens that way - I find it difficult to explain. Therefore, we need to find out what color space we have now set:

  Utils <IXPUtils> xpUtils;
 InterfacePtr <IXPManager> xpManager (xpUtils-> QueryXPManager (db));
 InterfacePtr <IDocument> doc (db, db-> GetRootUID (), UseDefaultIID ());
 AGMColorSpace * blendingSpace = xpManager-> GetDocumentBlendingSpace ();
Go
and convert if necessary:

  uint8 * outBuff = nil;
 int16 compCnt = 3, space = kRGBColorSpace;

 if (IsBeingFlattened (env, obj)) {
   TransformRGB2CMYKifNeeded (doc, blendingSpace, pictBuffer.get (), & outBuff, imageARGBBuff.length);
   compCnt = 4;  space = kCMYKColorSpace;
   pictBuffer.reset (outBuff);
 } 
Go
Now we are ready to set both the image and the alpha mask to it:

  AGMImageRecord imgR;
 memset (& imgR, 0, sizeof (AGMImageRecord));

 imgR.bounds.xMin = x;
 imgR.bounds.yMin = y;
 imgR.bounds.xMax = x + width;
 imgR.bounds.yMax = y + height;
 imgR.byteWidth = compCnt * width;
 imgR.colorSpace = space;
 imgR.bitsPerPixel = compCnt * 8;
 imgR.baseAddr = (void *) pictBuffer.get ();

 AGMImageRecord maskR;
 memset (& maskR, 0, sizeof (AGMImageRecord));
 maskR.bounds.xMin = x;
 maskR.bounds.yMin = y;
 maskR.bounds.xMax = x + width;
 maskR.bounds.yMax = y + height;
 maskR.byteWidth = width;
 maskR.colorSpace = kGrayColorSpace;
 maskR.bitsPerPixel = 8;
 maskR.baseAddr = (void *) maskBuffer.get ();
Go
It remains only to overtake the transformation matrix from the Java format to the InDesign format:

  PMMatrix transform (nativeTransformData [0], nativeTransformData [1],
 nativeTransformData [2],
    	 nativeTransformData [3], 
 nativeTransformData [4], 
 nativeTransformData [5]); 
Go
The final touch is to bring knowledge of the alpha channel mask to Indizayn:

  AGMPaint * alphaServer = xpUt-> CreateImagePaintServer (& maskR, & transform, 0, nil);
 PMRect bounds (x, y, width + x, height + y); 


  port-> SetAlphaServer (alphaServer, kTrue, stub); 


  port-> starttransparencygroup (bounds, blendingSpace, kFalse, kFalse); 

And you can finally draw a picture. We did it!

  port-> image (& imgR, transform, drawFlags); 
Go
The code for cleaning alpha servers and other things I don’t cite, as it is trivial.

Part last, or We take out the treasures of the defeated monster
image In this way, it was still possible for the good fellow to overcome the three-headed snake, and for the author to cross a hedgehog with a snake to draw one of the most poorly documented types of plug-ins in the Adobe InDesign SDK. Unfortunately, there was still room for improvement, for example, the same gradients, but the final result was quite satisfactory.
Here and the fairy tale is over, and who listened - well done!

Source: https://habr.com/ru/post/127574/


All Articles