Продолжение вот этого поста, автором которого является viklequick.
Шаг пятый, или Применяем стиль «пьяный мастер за работой»
Теперь, раз у нас уже есть примитивы, давайте применим цвета, альфа-канал и стили у линий.
Код в общем ничего сложного не содержит, всего лишь разбор BasicStroke. Если же это нечто другое — то это представимо в виде Shape и может быть так и отрисовано.
Далее мы получаем уровень альфа-канала для последующих shapes, и передаем его в InDesign — код тоже достаточ��о прост.
А вот с custom paints and composites ситуация значительно грустнее. Приходится их растеризовать самому.
Ситуация с цветом достаточно очевидна (разве что у цвета может быть явно задан альфа-канал), а вот все остальное требует пояснений. Обратите внимание, функция возвращает true/false. True означает что никаких трюков применено не было, и текущий shape надо отрисовать через fill(). А вот false означает что shape уже растеризован в виде картинки и его рисовать поверх картинки не надо.
Сам градиент пришлось отрисовать вот таким образом:
В этой функции градиент рисуется на offscreen image, и отсечением картинки является сам shape. Аналогичным образом мы отрисовываем и нестандартные Composite:
Так как мы отрисовываем уже трансформированный shape, то в drawImage мы задаем пустой AffineTransform.
Шаг шестой, или Добиваем супостата
Наконец-то мы дошли до самого интересного. Если для остальных случаев native реализация – это по сути однострочный код, то в случае с картинками это не так. Для начала приведу код для отрисовки картинки:
Код очень прост — задает отсечение, альфа-канал, получает битовую матрицу — и передает это все в InDesign. Получение битов очевидно:
А вот передача в InDesign обладает некоторыми особенностями. Рассмотрим их.
Легко заметить, это единственная функция, которая матрицу трансформаций передает в InDesign, а не отрабатывает самостоятельно. Можно было бы пойти другим путем, и трансформационную матрицу всегда передавать Индизайну, а не работать с transformed shapes. Но, к моему вящему сожалению, неумение рисовать градиенты средствами Индизайна привело вот к такому решению.
А теперь вернемся в С++ и рассмотрим нюансы со стороны плагина. Кода тут много, поэтому приведу только наиболее интересные выдержки, убрав тривиальности и обработку ошибок.
Начало очевидно:
// заполняем буферы по нашему ARGB
А вот дальше начинаются пляски с бубном.
Дело вот в чем — если у нас blending color space = RGB, то Индизайн хочет картинку в CMYK. А иначе — падает через раз, нанося богатырю страшную душевную рану, да так, что оный богатырь даже кушать не может. И наоборот — если blending color space = CMYK, то картинка должна быть в RGB. Найдено это было опытным путем, и почему все происходит именно так — затрудняюсь объяснить. Поэтому нам надо узнать, а какой же color space у нас сейчас выставлен:
и сконвертировать при необходимости:
Вот теперь мы готовы задать и картинку, и альфа-маску к ней:
Осталось только перегнать матрицу трансформации из формата Java в формат InDesign:
Последний штрих — надо донести до Индизайна знание о маске альфа-канала:
И можно наконец нарисовать картинку. We did it!
Код очистки альфа-серверов и прочего не привожу, так как он тривиален.
Часть последняя, или Выносим сокровища поверженного монстра
Вот таким образом удалось-таки добру молодцу побороть змея трехглавого, а автору — скрестить ежа с ужом нарисовать один из наиболее слабодокументированных видов плагинов в Adobe InDesign SDK. К сожалению, остались места для улучшения, например те же градиенты, но итоговый результат оказался вполне удовлетворительным.
Вот и сказочке конец, а кто слушал — молодец!
Шаг пятый, или Применяем стиль «пьяный мастер за работой»
Теперь, раз у нас уже есть примитивы, давайте применим цвета, альфа-канал и стили у линий.
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);
}
Код в общем ничего сложного не содержит, всего лишь разбор BasicStroke. Если же это нечто другое — то это представимо в виде Shape и может быть так и отрисовано. Далее мы получаем уровень альфа-канала для последующих shapes, и передаем его в InDesign — код тоже достаточ��о прост.
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);
} А вот с custom paints and composites ситуация значительно грустнее. Приходится их растеризовать самому.
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;
} Ситуация с цветом достаточно очевидна (разве что у цвета может быть явно задан альфа-канал), а вот все остальное требует пояснений. Обратите внимание, функция возвращает true/false. True означает что никаких трюков применено не было, и текущий shape надо отрисовать через fill(). А вот false означает что shape уже растеризован в виде картинки и его рисовать поверх картинки не надо.
Сам градиент пришлось отрисовать вот таким образом:
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(r.x, r.y, 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, r.x, r.y, r.width, r.height, new AffineTransform());
restoreClip();
}
pctx.dispose();
} В этой функции градиент рисуется на offscreen image, и отсечением картинки является сам shape. Аналогичным образом мы отрисовываем и нестандартные 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, r.x, r.y, r.width, r.height, new AffineTransform());
restoreClip();
}
} Так как мы отрисовываем уже трансформированный shape, то в drawImage мы задаем пустой AffineTransform.
Шаг шестой, или Добиваем супостата
Наконец-то мы дошли до самого интересного. Если для остальных случаев native реализация – это по сути однострочный код, то в случае с картинками это не так. Для начала приведу код для отрисовки картинки: 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;
} Код очень прост — задает отсечение, альфа-канал, получает битовую матрицу — и передает это все в InDesign. Получение битов очевидно:
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;
} А вот передача в InDesign обладает некоторыми особенностями. Рассмотрим их.
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);
} Легко заметить, это единственная функция, которая матрицу трансформаций передает в InDesign, а не отрабатывает самостоятельно. Можно было бы пойти другим путем, и трансформационную матрицу всегда передавать Индизайну, а не работать с transformed shapes. Но, к моему вящему сожалению, неумение рисовать градиенты средствами Индизайна привело вот к такому решению.
А теперь вернемся в С++ и рассмотрим нюансы со стороны плагина. Кода тут много, поэтому приведу только наиболее интересные выдержки, убрав тривиальности и обработку ошибок.
Начало очевидно:
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]);
// заполняем буферы по нашему ARGB
А вот дальше начинаются пляски с бубном.
Дело вот в чем — если у нас blending color space = RGB, то Индизайн хочет картинку в CMYK. А иначе — падает через раз, нанося богатырю страшную душевную рану, да так, что оный богатырь даже кушать не может. И наоборот — если blending color space = CMYK, то картинка должна быть в RGB. Найдено это было опытным путем, и почему все происходит именно так — затрудняюсь объяснить. Поэтому нам надо узнать, а какой же color space у нас сейчас выставлен:
Utils<IXPUtils> xpUtils; InterfacePtr<IXPManager> xpManager(xpUtils->QueryXPManager(db)); InterfacePtr<IDocument> doc(db, db->GetRootUID(), UseDefaultIID()); AGMColorSpace* blendingSpace = xpManager->GetDocumentBlendingSpace();
и сконвертировать при необходимости:
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);
}
Вот теперь мы готовы задать и картинку, и альфа-маску к ней:
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();
Осталось только перегнать матрицу трансформации из формата Java в формат InDesign:
PMMatrix transform(nativeTransformData[0], nativeTransformData[1],
nativeTransformData[2],
nativeTransformData[3],
nativeTransformData[4],
nativeTransformData[5]);
Последний штрих — надо донести до Индизайна знание о маске альфа-канала:
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);
И можно наконец нарисовать картинку. We did it!
port->image(&imgR, transform, drawFlags);
Код очистки альфа-серверов и прочего не привожу, так как он тривиален.
Часть последняя, или Выносим сокровища поверженного монстра
Вот таким образом удалось-таки добру молодцу побороть змея трехглавого, а автору — Вот и сказочке конец, а кто слушал — молодец!
