Related
I need to place/plot a pin(Small image to point the parts of person) on a image(For example: A image of person).
I am getting x,y,height,width values from server for a specific pin and i am creating one div element for each pin and assigning x,y,height,width values.
In Javascript, i am calculating view scale value in below mentioned way and multiply view scale with x,y,width,height and assign it into pin div element.
const screenwidth = screen.width;
const screenheight = screen.height;
viewscale = Math.min(screenwidth / mainImagewidth, screenheight / mainImageheight);
I am not able to place the pin on exact position of main image. Please help me if someone has idea of this logic.
Update:
Please find below the explanation through image.
Red Color rectangle is the screen. Green is the main image, let's say human image. Black color rectangle is a pin to describe a part in human image. I am getting x,y coordinates for this black colored rect pin from server.
Assuming I've understood this correctly, here's a possible demo solution:
First, define a config for your pin:
const pinConfig = {
width: 45,
height: 45,
offsetLeft: 40,
offsetTop: 75
};
Define a simple key/value map for getting the correct size type (width or height) when given an offset type (left or top):
const offsetTypeToSizeDimensionMap = {
left: 'width',
top: 'height'
};
Use a simple fn that calculates offset position relative to size. size / 2 because we need to compensate for the size of the pin, so positioning is based on the center of the element.
const calcRelativeOffsetPos = (offsetPos, size) => offsetPos - (size / 2);
Here's a style attribute string generating fn, accepts an object (our pinConfig above, basically):
const generateStylesString = (stylesConfig) => {
return Object.keys(stylesConfig).map((styleProp) => {
if (styleProp.includes('offset')){
const stylePropName = styleProp.split('offset')[1].toLowerCase();
const relativeSizeTypeByOffsetType = offsetTypeToSizeDimensionMap[stylePropName];
const calculatedRelativeOffsetPos = calcRelativeOffsetPos(stylesConfig[styleProp], stylesConfig[relativeSizeTypeByOffsetType]);
return stylePropName + ': ' + calculatedRelativeOffsetPos + 'px; ';
}
return styleProp + ': ' + stylesConfig[styleProp] + 'px; ';
}).join('');
};
Finally, set style attr to .child-parent node:
document.querySelector('.child-image').setAttribute('style', generateStylesString(pinConfig));
Here's an example on Codepen: https://codepen.io/Inlesco/pen/xLwjLy?editors=1010
If you need the React way, it's easy - just concat the generated inline styles string to a JSX element when mapping out the elements and that's it.
Feel free to provide feedback, so we can improve this :)
I have a simple pdf file, containing the words "Hello world", each in a different colour.
I'm loading the PDF, like this:
PDFJS.getDocument('test.pdf').then( onPDF );
function onPDF( pdf )
{
pdf.getPage( 1 ).then( onPage );
}
function onPage( page )
{
page.getTextContent().then( onText );
}
function onText( text )
{
console.log( JSON.stringify( text ) );
}
And I get a JSON output like this:
{
"items" : [{
"str" : "Hello ",
"dir" : "ltr",
"width" : 29.592,
"height" : 12,
"transform" : [12, 0, 0, 12, 56.8, 774.1],
"fontName" : "g_font_1"
}, {
"str" : "world",
"dir" : "ltr",
"width" : 27.983999999999998,
"height" : 12,
"transform" : [12, 0, 0, 12, 86.5, 774.1],
"fontName" : "g_font_1"
}
],
"styles" : {
"g_font_1" : {
"fontFamily" : "serif",
"ascent" : 0.891,
"descent" : 0.216
}
}
}
However, I've not been able to find a way to determine the colour of each word. When I render it, it renders properly, so I know the information is in there somewhere. Is there somewhere I can access this?
As Respawned alluded to, there is no easy answer that will work in all cases. That being said, here are two approaches which seem to work fairly well. Both having upsides and downsides.
Approach 1
Internally, the getTextContent method uses whats called an EvaluatorPreprocessor to parse the PDF operators, and maintain the graphic state. So what we can do is, implement a custom EvaluatorPreprocessor, overwrite the preprocessCommand method, and use it to add the current text color to the graphic state. Once this is in place, anytime a new text chunk is created, we can add a color attribute, and set it to the current color state.
The downsides to this approach are:
Requires modifying the PDFJS source code. It also depends heavily on
the current implementation of PDFJS, and could break if this is
changed.
It will fail in cases where the text is used as a path to be filled with an image. In some PDF creators (such as Photoshop), the way it creates colored text is, it first creates a clipping path from all the given text characters, and then paints a solid image over the path. So the only way to deduce the fill-color is by reading the pixel values from the image, which would require painting it to a canvas. Even hooking into paintChar wont be of much help here, since the fill color will only emerge at a later time.
The upside is, its fairly robust and works irrespective of the page background. It also does not require rendering anything to canvas, so it can be done entirely in the background thread.
Code
All the modifications are made in the core/evaluator.js file.
First you must define the custom evaluator, after the EvaluatorPreprocessor definition.
var CustomEvaluatorPreprocessor = (function() {
function CustomEvaluatorPreprocessor(stream, xref, stateManager, resources) {
EvaluatorPreprocessor.call(this, stream, xref, stateManager);
this.resources = resources;
this.xref = xref;
// set initial color state
var state = this.stateManager.state;
state.textRenderingMode = TextRenderingMode.FILL;
state.fillColorSpace = ColorSpace.singletons.gray;
state.fillColor = [0,0,0];
}
CustomEvaluatorPreprocessor.prototype = Object.create(EvaluatorPreprocessor.prototype);
CustomEvaluatorPreprocessor.prototype.preprocessCommand = function(fn, args) {
EvaluatorPreprocessor.prototype.preprocessCommand.call(this, fn, args);
var state = this.stateManager.state;
switch(fn) {
case OPS.setFillColorSpace:
state.fillColorSpace = ColorSpace.parse(args[0], this.xref, this.resources);
break;
case OPS.setFillColor:
var cs = state.fillColorSpace;
state.fillColor = cs.getRgb(args, 0);
break;
case OPS.setFillGray:
state.fillColorSpace = ColorSpace.singletons.gray;
state.fillColor = ColorSpace.singletons.gray.getRgb(args, 0);
break;
case OPS.setFillCMYKColor:
state.fillColorSpace = ColorSpace.singletons.cmyk;
state.fillColor = ColorSpace.singletons.cmyk.getRgb(args, 0);
break;
case OPS.setFillRGBColor:
state.fillColorSpace = ColorSpace.singletons.rgb;
state.fillColor = ColorSpace.singletons.rgb.getRgb(args, 0);
break;
}
};
return CustomEvaluatorPreprocessor;
})();
Next, you need to modify the getTextContent method to use the new evaluator:
var preprocessor = new CustomEvaluatorPreprocessor(stream, xref, stateManager, resources);
And lastly, in the newTextChunk method, add a color attribute:
color: stateManager.state.fillColor
Approach 2
Another approach would be to extract the text bounding boxes via getTextContent, render the page, and for each text, get the pixel values which reside within its bounds, and take that to be the fill color.
The downsides to this approach are:
The computed text bounding boxes are not always correct, and in some cases may even be off completely (eg: rotated text). If the bounding box does not cover at least partially the actual text on canvas, then this method will fail. We can recover from complete failures, by checking that the text pixels have a color variance greater than a threshold. The rationale being, if bounding box is completely background, it will have little variance, in which case we can fallback to a default text color (or maybe even the color of k nearest-neighbors).
The method assumes the text is darker than the background. Otherwise, the background could be mistaken as the fill color. This wont be a problem is most cases, as most docs have white backgrounds.
The upside is, its simple, and does not require messing with the PDFJS source-code. Also, it will work in cases where the text is used as a clipping path, and filled with an image. Though this can become hazy when you have complex image fills, in which case, the choice of text color becomes ambiguous.
Demo
http://jsfiddle.net/x2rajt5g/
Sample PDF's to test:
https://www.dropbox.com/s/0t5vtu6qqsdm1d4/color-test.pdf?dl=1
https://www.dropbox.com/s/cq0067u80o79o7x/testTextColour.pdf?dl=1
Code
function parseColors(canvasImgData, texts) {
var data = canvasImgData.data,
width = canvasImgData.width,
height = canvasImgData.height,
defaultColor = [0, 0, 0],
minVariance = 20;
texts.forEach(function (t) {
var left = Math.floor(t.transform[4]),
w = Math.round(t.width),
h = Math.round(t.height),
bottom = Math.round(height - t.transform[5]),
top = bottom - h,
start = (left + (top * width)) * 4,
color = [],
best = Infinity,
stat = new ImageStats();
for (var i, v, row = 0; row < h; row++) {
i = start + (row * width * 4);
for (var col = 0; col < w; col++) {
if ((v = data[i] + data[i + 1] + data[i + 2]) < best) { // the darker the "better"
best = v;
color[0] = data[i];
color[1] = data[i + 1];
color[2] = data[i + 2];
}
stat.addPixel(data[i], data[i+1], data[i+2]);
i += 4;
}
}
var stdDev = stat.getStdDev();
t.color = stdDev < minVariance ? defaultColor : color;
});
}
function ImageStats() {
this.pixelCount = 0;
this.pixels = [];
this.rgb = [];
this.mean = 0;
this.stdDev = 0;
}
ImageStats.prototype = {
addPixel: function (r, g, b) {
if (!this.rgb.length) {
this.rgb[0] = r;
this.rgb[1] = g;
this.rgb[2] = b;
} else {
this.rgb[0] += r;
this.rgb[1] += g;
this.rgb[2] += b;
}
this.pixelCount++;
this.pixels.push([r,g,b]);
},
getStdDev: function() {
var mean = [
this.rgb[0] / this.pixelCount,
this.rgb[1] / this.pixelCount,
this.rgb[2] / this.pixelCount
];
var diff = [0,0,0];
this.pixels.forEach(function(p) {
diff[0] += Math.pow(mean[0] - p[0], 2);
diff[1] += Math.pow(mean[1] - p[1], 2);
diff[2] += Math.pow(mean[2] - p[2], 2);
});
diff[0] = Math.sqrt(diff[0] / this.pixelCount);
diff[1] = Math.sqrt(diff[1] / this.pixelCount);
diff[2] = Math.sqrt(diff[2] / this.pixelCount);
return diff[0] + diff[1] + diff[2];
}
};
This question is actually extremely hard if you want to do it to perfection... or it can be relatively easy if you can live with solutions that work only some of the time.
First of all, realize that getTextContent is intended for searchable text extraction and that's all it's intended to do.
It's been suggested in the comments above that you use page.getOperatorList(), but that's basically re-implementing the whole PDF drawing model in your code... which is basically silly because the largest chunk of PDFJS does exactly that... except not for the purpose of text extraction but for the purpose of rendering to canvas. So what you want to do is to hack canvas.js so that instead of just setting its internal knobs it also does some callbacks to your code. Alas, if you go this way, you won't be able to use stock PDFJS, and I rather doubt that your goal of color extraction will be seen as very useful for PDFJS' main purpose, so your changes are likely not going to get accepted upstream, so you'll likely have to maintain your own fork of PDFJS.
After this dire warning, what you'd need to minimally change are the functions where PDFJS has parsed the PDF color operators and sets its own canvas painting color. That happens around line 1566 (of canvas.js) in function setFillColorN. You'll also need to hook the text render... which is rather a character renderer at canvas.js level, namely CanvasGraphics_paintChar around line 1270. With these two hooked, you'll get a stream of callbacks for color changes interspersed between character drawing sequences. So you can reconstruct the color of character sequences reasonably easy from this.. in the simple color cases.
And now I'm getting to the really ugly part: the fact that PDF has an extremely complex color model. First there are two colors for drawing anything, including text: a fill color and stroke (outline) color. So far not too scary, but the color is an index in a ColorSpace... of which there are several, RGB being only one possibility. Then there's also alpha and compositing modes, so the layers (of various alphas) can result in a different final color depending on the compositing mode. And the PDFJS has not a single place where it accumulates color from layers.. it simply [over]paints them as they come. So if you only extract the fill color changes and ignore alpha, compositing etc.. it will work but not for complex documents.
Hope this helps.
There's no need to patch pdfjs, the transform property gives the x and y, so you can go through the operator list and find the setFillColor op that precedes the text op at that point.
I asynchronously receive new positions of element. Each time I receive the position I calculate the offset and move the element (set), like this:
asynchReceiveData(id,function(newposition){
var offset = {};
var oldposition = getOldPosition(markerArray[id]); //returns object with old x and y
offset.x = (newposition.x - oldposition.x);
offset.y = (newposition.y - oldposition.y);
markerArray[id].entireSet.stop()
.animate({ transform:"...T"+offset.x+","+offset.y }, 400);
//i also tried without .stop()
});
For example:
with each update the set should move 50px to the right, after 10 slow updates (lets say one update per 2 seconds), the set is 500px to the right, everything is OK.
Problem is, when I receive too many new positions too fast:
(e.g. one update per 200ms),then the set is 300, or 350 or 400 or 450 instead of 500px to the right.
I think the problem is that the animation does not have enough time to finish before new animation is triggered. I tried lowering the animation time from 400ms to 200, but with little success, it still sometimes happened.
Everything works fine, when I do not use animation and do just this:
markerArray[id].entireSet.transform("...T"+offset.x+","+offset.y);
But I would like to have this animations. Do you have any suggestion how to fix this?
So.. after few tries I came to a solution:
After the end of every animation I check the real position of element (with getBBox()) and compare it with expected position. If it differs I move the element by the difference; In code:
asynchReceiveData(id,function(newposition){
var offset = {};
var oldposition = getOldPosition(markerArray[id]); //returns object with old x and y
offset.x = (newposition.x - oldposition.x);
offset.y = (newposition.y - oldposition.y);
markerArray[id].entireSet.stop().animate({ transform:"...T"+offset.x+","+offset.y}, 500,
function () {
var o = {};
o.x = markerArray[id].x - markerArray[id].circleObj.getBBox().cx;
o.y = markerArray[id].y - markerArray[id].circleObj.getBBox().cy;
markerArray[id].entireSet.transform("...T"+o.x+","+o.y);
});
});
Sometimes it's not very smooth (a bit laggy), but anyway, it solves the problem.
I'm trying to trigger an event half-way through the progress (not time) of a transition. It sounds simple, but since the transition can have any curve it's quite tricky. In my particular case it's not going to be paused or anything so that consideration is out of the way.
(Simplified) essentially I could trigger an animation on a modifier like this:
function scaleModifierTo(stateModifier, scale, animationDuration) {
stateModifier.setTransform(
Transform.scale(scale, scale, scale),
{
duration: animationDuration,
curve: this.options.curve
}
);
}
When the interpolated state of the Transitionable hits 0.5 (half-way through) I want to trigger a function.
I haven't dug that deep behind in the source of famo.us yet, but maybe need to do something like
subclass something and add the possibility to listen when the state passes through a certain point?
reverse the curve defined and use a setTimeout (or try to find a proximity using a few iterations of the chosen curve algorithm (ew))
Is it possible to do this easily? What route should I go down?
I can think of a couple of ways to achieve such, and both lend to the use of Modifier over StateModifier. If you are new, and haven't really had the chance to explore the differences, Modifier consumes state from the transformFrom method which takes a function that returns a transform. This is where we can use our own Transitionable to supply state over the lifetime of our modifier.
To achieve what you wish, I used a Modifier with a basic transformFrom that will alter the X position of the surface based on the value of the Transitionable. I can then monitor the transitionable to determine when it is closest, or in my case greater than or equal to half of the final value. The prerender function will be called and checked on every tick of the engine, and is unbinded when we hit the target.
Here is that example..
var Engine = require('famous/core/Engine');
var Surface = require('famous/core/Surface');
var Modifier = require('famous/core/Modifier');
var Transform = require('famous/core/Transform');
var Transitionable = require('famous/transitions/Transitionable');
var SnapTransition = require('famous/transitions/SnapTransition');
Transitionable.registerMethod('snap',SnapTransition);
var snap = { method:'snap', period:1000, damping:0.6};
var context = Engine.createContext();
var surface = new Surface({
size:[200,200],
properties:{
backgroundColor:'green'
}
});
surface.trans = new Transitionable(0);
surface.mod = new Modifier();
surface.mod.transformFrom(function(){
return Transform.translate(surface.trans.get(),0,0);
});
context.add(surface.mod).add(surface);
function triggerTransform(newValue, transition) {
var prerender = function(){
var pos = surface.trans.get();
if (pos >= (newValue / 2.0)) {
// Do Something.. Emit event etc..
console.log("Hello at position: "+pos);
Engine.removeListener('prerender',prerender);
}
}
Engine.on('prerender',prerender);
surface.trans.halt();
surface.trans.set(newValue,transition);
}
surface.on('click',function(){ triggerTransform(400, snap); });
The downside of this example is the fact that you are querying the transitionable twice. An alternative is to add your transitionable check right in the transformFrom method. This could get a bit strange, but essentially we are modifying our transformFrom method until we hit our target value, then we revert back to the original transformFrom method.. triggerTransform would be defined as follows..
Hope this helps!
function triggerTransform(newValue, transition) {
surface.mod.transformFrom(function(){
pos = surface.trans.get()
if (pos >= newValue/2.0) {
// Do something
console.log("Hello from position: " + pos)
surface.mod.transformFrom(function(){
return Transform.translate(surface.trans.get(),0,0);
});
}
return Transform.translate(pos,0,0)
})
surface.trans.set(newValue,transition);
}
Thank you for your responses, especially #johntraver for the prerender event, I wasn't aware of the existence of that event.
I realised it made more sense that I should handle this logic together with my move animation, not the scale one. Then, I ended up using a (very hacky) way of accessing the current state of the transition and by defining a threshold in px I can trigger my function when needed.
/**
* Move view at index to a specified offset
* #param {Number} index
* #param {Number} xOffset xOffset to move to
* #param {Object} animation Animation properties
* #return void
*/
function moveView(index, xOffset, animation) {
var rectModifier = this._views[index].modifiers.rect;
var baseXOffset = rectModifier._transformState.translate.state[0];
// After how long movement is reflow needed?
// for the sake of this example I use half the distance of the animation
var moveThreshold = Math.abs(baseXOffset - xOffset)/2;
/**
* Callback function triggered on each animation frame to see if the view is now covering
* the opposite so we can trigger a reflow of the z index
* #return void
*/
var prerender = function() {
var numPixelsMoved = Math.abs(baseXOffset - rectModifier._transformState.translate.state[0]);
if (numPixelsMoved > moveThreshold) {
Engine.removeListener('prerender', prerender);
// trigger a method when this is reached
_reflowZIndex.call(this);
}
}.bind(this);
rectModifier.setTransform(
Transform.translate(xOffset, 0, 0),
animation,
function() {
Engine.removeListener('prerender', prerender);
}
);
Engine.on('prerender', prerender);
}
Obviously the ._transformState.translate.state[0] is a complete hack, but I couldn't figure out of getting this value in a clean way without adding my own Transitionable, which I don't want. If there is a cleaner way of finding the current state as a Number between 0.0-1.0 that would be ace; anyone knows of one?
I'm using the YUI 2.7 library to handle a dual-slider (range slider) control in a webpage.
It works great-- however, I wanted to allow users to switch the range values by Ajax-- effectively changing the price range from "0-50,000" to a subset (eg. "50-250") without reloading the page.
The problem is that it appears the values from the existing slider do not get reset, even when I explicitly set them back to NULL inside the function to "rebuild" the slider.
The slider handles appear out of position after the ajax request, (way off the scale to the right) and the values of the slider apparently randomly fluctuate.
Is there a way to explicitly destroy the YUI slider object, beyond setting its reference to null? Or do I just need to redeclare the scale and min/max values somehow?
Thanks for any help (I'll try to post a link to an example asap)
here's the code:
function slider(bg,minthumb,maxthumb,minvalue,maxvalue,startmin,startmax,aSliderName,soptions) {
var scaleFactor = null;
var tickSize = null;
var range = null;
var dual_slider = null;
var initVals = null;
var Dom = null;
range = options.sliderLength;
if ((startmax - startmin) < soptions.sliderLength) {
tickSize = (soptions.sliderLength / (startmax - startmin));
}else{
tickSize = 1;
}
initVals = [ 0,soptions.sliderLength ], // Values assigned during instantiation
//Event = YAHOO.util.Event,
dual_slider,
scaleFactor = ((startmax - startmin) / soptions.sliderLength);
dual_slider = YAHOO.widget.Slider.getHorizDualSlider(
bg,minthumb,maxthumb,range, tickSize, initVals);
dual_slider.subscribe("change", function(instance) {
priceMin = (dual_slider.minVal * scaleFactor) + startmin;
priceMax = (dual_slider.maxVal * scaleFactor) + startmin;
});
dual_slider.subscribe("slideEnd", function(){ alert(priceMin + ' ' + priceMax); });
return dual_slider;
}
Store the startmin, startmax, and scaleFactor on the dual_slider object, then in your ajax callback, update those properties with new values. Change your change event subscriber to reference this.startmin, this.startmax, and this.scaleFactor.
Slider and DualSlider only really understand the pixel offsets of the thumbs, and report the values as such. As you've done (and per most Slider examples), you need to apply a conversion factor to translate a pixel offset to a "value". This common idiom has been rolled into the core logic of the YUI 3 Slider (though there isn't yet a DualSlider in the library).
Here's an example that illustrates dynamically updating value ranges:
http://yuiblog.com/sandbox/yui/v282/examples/slider/slider_factor_change.html