Repository: interconnectit/my-eyes-are-up-here Branch: master Commit: 268cec7f4d3a Files: 16 Total size: 48.0 KB Directory structure: gitextract_s0unzbn_/ ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── README.md ├── assets/ │ ├── css/ │ │ └── main.css │ └── js/ │ └── main.js ├── bower.json ├── composer.json ├── includes/ │ ├── class-meauh-admin.php │ ├── class-meauh-ajax.php │ └── class-meauh-attachment.php ├── languages/ │ ├── meauh.pot │ └── my-eyes-are-up-here.pot ├── my-eyes-are-up-here.php ├── package.json └── readme.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .svn bower_components node_modules npm-debug.log ================================================ FILE: .jshintrc ================================================ { "bitwise": true, "browser": true, "curly": true, "eqeqeq": true, "eqnull": true, "esnext": true, "immed": true, "jquery": true, "latedef": true, "newcap": true, "noarg": true, "node": true, "strict": false, "trailing": true } ================================================ FILE: Gruntfile.js ================================================ 'use strict'; module.exports = function (grunt) { require('load-grunt-tasks')(grunt); require('time-grunt')(grunt); var jsFileList = [ 'bower_components/jquery.facedetection/dist/jquery.facedetection.js', 'assets/js/main.js' ]; grunt.initConfig({ makepot: { options: { type: 'wp-plugin', domainPath: 'languages', potHeaders: { 'report-msgid-bugs-to': 'https://github.com/interconnectit/my-eyes-are-up-here/issues', 'language-team': 'LANGUAGE ' } }, dist: { options: { potFilename: 'my-eyes-are-up-here.pot', exclude: [ 'assets/.*' ] } } }, checktextdomain: { options: { text_domain: 'my-eyes-are-up-here', keywords: [ '__:1,2d', '_e:1,2d', '_x:1,2c,3d', 'esc_html__:1,2d', 'esc_html_e:1,2d', 'esc_html_x:1,2c,3d', 'esc_attr__:1,2d', 'esc_attr_e:1,2d', 'esc_attr_x:1,2c,3d', '_ex:1,2c,3d', '_n:1,2,4d', '_nx:1,2,4c,5d', '_n_noop:1,2,3d', '_nx_noop:1,2,3c,4d' ] }, files: { src: [ '**/*.php', '!assets/**', '!bower_components/**', '!node_modules/**', ], expand: true } }, autoprefixer: { options: { browsers: [ 'last 2 versions', 'ie 8', 'ie 9', 'android 2.3', 'android 4', 'opera 12' ] }, dist: { src: 'assets/css/main.min.css' } }, cssmin: { options: { compatibility: 'ie8', keepSpecialComments: '*', noAdvanced: true }, dist: { files: [{ expand: true, cwd: 'assets/css', src: ['*.css', '!*.min.css'], dest: 'assets/css', ext: '.min.css' }] } }, jshint: { options: { jshintrc: '.jshintrc' }, all: [ 'Gruntfile.js', 'assets/js/*.js', '!assets/js/scripts.min.js' ] }, uglify: { options: { preserveComments: 'some' }, dist: { src: jsFileList, dest: 'assets/js/scripts.min.js' } }, }); grunt.registerTask('build', [ 'makepot', 'autoprefixer', 'cssmin', 'jshint', 'uglify' ]); }; ================================================ FILE: README.md ================================================ My eyes are up here =================== Face detection for generating cropped thumbnails in WordPress. Avoiding automatically generated crotch shots since 2013. ## Why would I want this? Consider a common problem with automatically generated thumbnails in WordPress themes. You need an image of a precise width and height to fit into the design but you never know what images people are uploading. You could control the width and height of the standard WP thumbnail sizes and let folks alter the crop as necessary themselves but if you have more than a few custom image sizes you can't alter the crop of those. Let's say you have a portrait image of someone and your theme needs a landscape crop of the image. WP centers the crop so you'll get an image of the persons crotch... Not ideal. I assume. This plugin detects faces in an image and centers the crop using an average of all the faces it finds. ``` Portrait image: +-----------+ | | | O | | --|-- | | | | | | | | | | | | | | +-----------+ Cropped landscape version with default WP cropping: +-----------+ | --|-- | | | | | | | | +-----------+ Cropped landscape version using this plugin: +-----------+ | | | O | | --|-- | +-----------+ ``` ================================================ FILE: assets/css/main.css ================================================ .face-detect-panel { margin-bottom: 15px; } .face-detection-ui { overflow: hidden; } .face-detection-image { position: relative; overflow: hidden; float: left; max-width: 100%; } .face-detection-image.active { cursor: crosshair; } .face-detection-image img { max-width: 100%; vertical-align: middle; height: auto; width: auto; } .face-detection-image .hotspot { position: absolute; max-width: 150px; min-width: 10px; height: 0; border: 0px solid #ccc; border: solid 1px rgba(230, 230, 230, .85); background: rgba(255, 155, 155, .5); border-radius: 50%; } .hotspot.face { background: rgba(155, 155, 255, .5); } .hotspot.normal { cursor: pointer; } .face-detect-panel .button { margin-right: 5px; margin-bottom: 5px; display: inline-block; } .status.loading { padding-left: 20px; background: url(../img/spin.gif) no-repeat left center; background-size: contain; } .found-faces img, .found-faces canvas { position: static; width: 40px; height: auto; margin: 10px 10px 0 0; display: inline-block; } .post-thumbnail-preview { background: #f5f5f5; background-image: -webkit-gradient(linear, left bottom, left top, from(#f5f5f5), to(#fafafa)); background-image: -webkit-linear-gradient(bottom, #f5f5f5, #fafafa); background-image: -moz-linear-gradient(bottom, #f5f5f5, #fafafa); background-image: -o-linear-gradient(bottom, #f5f5f5, #fafafa); background-image: linear-gradient(to top, #f5f5f5, #fafafa); overflow: auto; margin-bottom: 10px; padding: 10px; border: 1px solid #dfdfdf; border-radius: 3px; font-size: 12px; line-height: 0.9; width: 100%; box-sizing: border-box; } .post-thumbnail-preview .preview-wrap { display: inline-block; margin-top: 5px; margin-right: 5px; background: url(../img/spin.gif) no-repeat center; } .post-thumbnail-preview .preview-wrap img { max-width: 100%; width: auto; max-height: 50px; height: auto; } .media-modal .compat-field-face_detection .label { margin-right: 0; } .face-detect-large-hidden, .face-detect-large-hidden-copy { position: absolute; left: 9999px; top: -9999px; } ================================================ FILE: assets/js/main.js ================================================ window.hotspots = {}; // prevent errors while logging to browsers that support it if (!window.console) { window.console = { log: function () { } }; } ;(function ($) { function Hotspots() { var that = this; $.extend(that, { _construct: function () { // bind behaviour to buttons $(document).on('click', '.face-detection-activate', function () { that.set_context(this); that.get_image(that.detect_faces); }); $(document).on('click', '.add-hotspots', function () { that.set_context(this); that.get_image(that.add_hotspots); }); }, attachment_id: null, el: null, image: null, hidden: null, images: null, $context: null, $status_box: null, hotspots: [], faces: [], set_context: function (el) { that.el = el; that.attachment_id = $(el).data('attachment-id'); that.$ui = $(el).parents('.face-detection-ui'); that.$context = $(el).parents('.face-detect-panel'); that.$status_box = that.$context.find('.status'); }, // request full image get_image: function (callback) { callback = callback || function () { return false; }; if (that.image && that.$ui.find('.face-detection-image img').length) { return callback(); } that.update_status('Loading full size image', true); $.post(meauh.ajax_url, { action: 'meauh_get_image', nonce: meauh.get_image_nonce, attachment_id: that.attachment_id }, function (response) { if (response.success) { // set our image that.image = new Image(); // save for later that.images = response.data; // set source to original uncropped image that.image.src = response.data.original[0]; $(that.image) .appendTo('.face-detection-image') .on('load', function () { that.update_status('Image loaded'); // add our large off-screen sampler for pixastic etc... if (!$('.face-detect-large-hidden').length) { $('body').append(''); } $('.face-detect-large-hidden').attr('src', response.data.original[0]); // show current data that.show_existing($('.face-detection-image').data('hotspots')); that.show_existing($('.face-detection-image').data('faces'), 'face'); return callback(); }); } }, 'json'); return false; }, update_status: function (status, loading) { loading = loading || false; that.$status_box.html(status); if (loading) { that.$status_box.addClass('loading'); } else { that.$status_box.removeClass('loading'); } }, detect_faces: function () { // Remove the previous copy, end up with one for every button press otherwise. $('.face-detect-large-hidden-copy').remove(); var $found_box = that.$context.find('.found-faces'), image = $('.face-detect-large-hidden').get(0), image_copy = $(image) .clone() .removeClass('face-detect-large-hidden') .addClass('face-detect-large-hidden-copy') .appendTo('body') .get(0); if ($(that.el).hasClass('has-faces')) { $(image_copy).remove(); //$found_box.html( '' ); $(that.el) .removeClass('has-faces') .html('Detect faces'); $('.face-detection-image') .data('faces', '') .find('.face') .remove(); return that.save({faces: 0}); } // face detection return $(image_copy).faceDetection({ complete: function (faces) { // update status - found faces that.faces = faces; if (!that.faces.length) { that.update_status('No faces were found'); return; } // allow removal of found faces $(that.el) .addClass('has-faces') .html('Forget found faces'); that.update_status('Found ' + that.faces.length + ' faces, re-cropping thumbnails', true); that.show_existing(that.faces, 'face'); // cleanup $(image_copy).remove(); // save data & regen that.save({faces: that.faces}); }, error: function (img, code, message) { // update status - error, message console.log('error', message, img); that.update_status('Error (' + code + '): ' + message); } }); }, show_existing: function (data, type) { type = type || 'normal'; var width = $(that.image).width(), correction = that.images.original[1] / width, hotspot_width; if ('undefined' !== typeof data && data.length) { $.each(data, function (i, hotspot) { that.add_hotspot({ x: (hotspot.x / correction), y: (hotspot.y / correction), width: hotspot.width / correction, type: type }); }); } }, add_hotspots: function () { var width = $(that.image).width(), hotspot_width = width * 0.15, correction = that.images.original[1] / width; // activate hotspots if (!$('.face-detection-image').hasClass('active')) { // edit button $(that.el) .addClass('active') .html('Finish adding hotspots'); that.$ui.find('button').not(that.el).attr('disabled', 'disabled'); that.update_status('Click on the image below to add hotspots. Clicking a hotspot will remove it.'); // bind hotspot toggling $(that.image).on('click.hotspots', that.hotspot_click); $('.face-detection-image').addClass('active'); // deactivate & save } else { // edit button $(that.el) .removeClass('active') .html('Edit hotspots'); // remove hotspot toggling $(that.image).off('click.hotspots'); that.hotspots = []; $('.face-detection-image .hotspot').not('.face').each(function () { that.hotspots.push({ width: Math.round($(this).width() * correction), x: Math.round(( $(this).position().left ) * correction), y: Math.round(( $(this).position().top ) * correction) }); }); $('.face-detection-image').removeClass('active'); if (!that.hotspots.length) { that.hotspots = 0; } // save data that.save({hotspots: that.hotspots}); } }, hotspot_click: function (e) { var width = $(that.image).width(), hotspot_maxwidth = 150, hotspot_width = width * 0.15 > hotspot_maxwidth ? hotspot_maxwidth : width * 0.15, hotspot_offset = hotspot_width / 2; // Firefox doesn't do offsetX/Y so need to do something a little more complex that.add_hotspot({ x: ( e.offsetX || e.clientX - ( $(e.target).offset().left - window.scrollX ) ) - hotspot_offset, y: ( e.offsetY || e.clientY - ( $(e.target).offset().top - window.scrollY ) ) - hotspot_offset }); }, add_hotspot: function (hotspot) { var width = $(that.image).width(), height = $(that.image).height(), $parent = $('.face-detection-image'), hotspot_maxwidth = 150, hotspot_width = width * 0.15 > hotspot_maxwidth ? hotspot_maxwidth : width * 0.15; hotspot = $.extend({ x: 0, y: 0, width: hotspot_width, // default 15% wide, max-width 120px type: 'normal' }, hotspot); // Prevent hotspots from being placed outside edges of image. hotspot.x = Math.max((0 - (hotspot.width / 2)), Math.min(hotspot.x, (width - (hotspot.width / 2)))); hotspot.y = Math.max((0 - (hotspot.width / 2)), Math.min(hotspot.y, (height - (hotspot.width / 2)))); $('
') .css({ left: ( ( hotspot.x / width ) * 100 ) + '%', top: ( ( hotspot.y / height ) * 100 ) + '%', width: ( ( hotspot.width / width ) * 100 ) + '%', paddingBottom: ( ( hotspot.width / width ) * 100 ) + '%' }) .attr('title', hotspot.type === 'normal' ? 'Click to toggle on/off' : '') .appendTo($parent) .click(function () { if (!$(this).hasClass('face') && $parent.hasClass('active')) { $(this).remove(); } }); }, // show a cropped thumbnail preview preview: function () { var $previews = $('.post-thumbnail-preview img'), previews_length = $previews.length; that.update_status('Updating preview', true); $previews.each(function (i) { if (!that.images[$(this).data('size')]) { return; } $(this) .fadeTo(300, 0.25) .attr('src', that.images[$(this).data('size')][0] + '?t=' + new Date().getTime()) .on('load', function () { $(this).fadeTo(300, 1); if (i === previews_length - 1) { that.update_status(''); } }); }); }, save: function (data) { that.update_status('Re-cropping thumbnails', true); that.$ui.find('button').attr('disabled', 'disabled'); $.post(meauh.ajax_url, $.extend({ action: 'meauh_save_image', nonce: meauh.save_image_nonce, attachment_id: that.attachment_id }, data), function (response) { if (response.success) { that.update_status('Thumbnails re-cropped'); $.extend(that.images, response.data.resized); that.preview(); } else { that.update_status('No thumbnails were re-cropped'); } that.$ui.find('button').removeAttr('disabled'); }, 'json'); } }); // initialise that._construct(); return that; } // initialise window.hotspots = new Hotspots(); }(jQuery)); ================================================ FILE: bower.json ================================================ { "name": "my-eyes-are-up-here", "homepage": "https://interconnectit.com", "authors": [ "Evgenii Nasyrov " ], "license": "MIT", "private": true, "dependencies": { "jquery.facedetection": "~2.0.2" } } ================================================ FILE: composer.json ================================================ { "name": "interconnectit/my-eyes-are-up-here", "type": "wordpress-plugin", "license": "GPLv2", "description": "My Eyes Are Up Here helps you control how WordPress generates thumbnails.", "homepage": "https://github.com/interconnectit/my-eyes-are-up-here", "authors": [ { "name": "interconnect/it", "email": "support@interconnectit.com", "homepage": "https://github.com/interconnectit" } ], "keywords": [ "wordpress" ], "support": { "issues": "https://github.com/interconnectit/my-eyes-are-up-here/issues" }, "require": { "php": ">=5.4.0", "composer/installers": "~1.0" } } ================================================ FILE: includes/class-meauh-admin.php ================================================ includes(); } /** * Assets */ public function assets() { // Main script. wp_enqueue_script( 'meauh-main', meauh()->plugin_url() . '/assets/js/scripts.min.js', array( 'jquery' ), filemtime( meauh()->plugin_path() . '/assets/js/scripts.min.js' ), true ); // Main script variables. wp_localize_script( 'meauh-main', 'meauh', array( 'ajax_url' => meauh()->ajax_url(), 'get_image_nonce' => wp_create_nonce( MEAUH_Ajax::NONCE_GET_IMAGE ), 'save_image_nonce' => wp_create_nonce( MEAUH_Ajax::NONCE_SAVE_IMAGE ), ) ); // Main style. wp_enqueue_style( 'meauh-main', meauh()->plugin_url() . '/assets/css/main.min.css', array(), filemtime( meauh()->plugin_path() . '/assets/css/main.min.css' ), 'all' ); } /** * Includes */ protected function includes() { } } MEAUH_Admin::init(); ================================================ FILE: includes/class-meauh-ajax.php ================================================ false, 'save_image' => false, ); /** * Init */ public static function init() { $self = new self; foreach ( self::$events as $event => $nopriv ) { add_action( 'wp_ajax_meauh_' . $event, array( $self, $event ) ); if ( $nopriv ) { add_action( 'wp_ajax_nopriv_meauh_' . $event, array( $self, $event ) ); } } add_action( 'wp_ajax_save-attachment-compat', 'save_image', 0, 1 ); } /** * Get an image */ public function get_image() { check_ajax_referer( self::NONCE_GET_IMAGE, 'nonce' ); $attachment_id = isset( $_POST['attachment_id'] ) ? absint( $_POST['attachment_id'] ) : false; if ( $attachment_id && $this->is_attachment( $attachment_id ) ) { wp_send_json_success( array( 'original' => wp_get_attachment_image_src( $attachment_id, 'full' ), ) ); } else { wp_send_json_error(); } } /** * Save an image */ public function save_image() { check_ajax_referer( self::NONCE_SAVE_IMAGE, 'nonce' ); $attachment_id = isset( $_POST['attachment_id'] ) ? absint( $_POST['attachment_id'] ) : false; if ( ! $this->is_attachment( $attachment_id ) ) { wp_send_json_error(); } // WP Offload S3 Compatibility. $this->as3cf_compatibility( $attachment_id ); // Save faces. $this->save_image_faces( $attachment_id ); // Save hotspots. $this->save_image_hotspots( $attachment_id ); // Regenerate thumbs. $resized = MEAUH_Attachment::regenerate( $attachment_id ); if ( $resized ) { wp_send_json_success( array( 'resized' => $resized, ) ); } } /** * Is attachment * * @param int $attachment_id Attachment id. * * @return bool */ protected function is_attachment( $attachment_id ) { return $attachment_id && get_post( $attachment_id ) && 'attachment' === get_post_type( $attachment_id ); } /** * WP Offload S3 Compatibility * * @param $attachment_id Attachment ID. */ protected function as3cf_compatibility( $attachment_id ) { if ( ! is_plugin_active( 'amazon-s3-and-cloudfront/wordpress-s3.php' ) ) { return; } $file = get_attached_file( $attachment_id ); file_put_contents( $file, file_get_contents( $file ) ); } /** * Save image faces * * @param int $attachment_id Attachment ID. */ protected function save_image_faces( $attachment_id ) { if ( ! empty( $_POST['faces'] ) ) { update_post_meta( $attachment_id, 'faces', array_filter( $_POST['faces'], array( $this, 'filter' ) ) ); } else { delete_post_meta( $attachment_id, 'faces' ); } } /** * Save image hotspots * * @param int $attachment_id Attachment ID. */ protected function save_image_hotspots( $attachment_id ) { if ( ! empty( $_POST['hotspots'] ) ) { update_post_meta( $attachment_id, 'hotspots', array_filter( $_POST['hotspots'], array( $this, 'filter' ) ) ); } else { delete_post_meta( $attachment_id, 'hotspots' ); } } /** * Make sure we got solid data * * @param array $value Values to filter. * * @return array */ protected function filter( $value ) { $allowed_keys = array( 'x', 'y', 'width', ); $value = array_intersect_key( $value, array_flip( $allowed_keys ) ); if ( isset( $value['x'], $value['y'], $value['width'] ) ) { return array_filter( $value, 'is_numeric' ); } else { return array(); } } } MEAUH_Ajax::init(); ================================================ FILE: includes/class-meauh-attachment.php ================================================ $attachment_id, 'error' => $metadata->get_error_message() ); } if ( empty( $metadata ) ) { return array( 'id' => $attachment_id, 'error' => __( 'Unknown failure reason', 'my-eyes-are-up-here' ) ); } // If this fails, then it just means that nothing was changed. wp_update_attachment_metadata( $attachment_id, $metadata ); $sizes = self::get_cropped_sizes(); $resized = array(); foreach ( $sizes as $size => $atts ) { $resized[ $size ] = wp_get_attachment_image_src( $attachment_id, $size ); } return $resized; } /** * Hacky use of attached_file filters to get current attachment ID being resized * Used to store face location and dimensions * * @param string $file File name. * @param int $attachment_id Attachment id. * * @return string */ public function set_attachment_id( $file, $attachment_id ) { $this->faces = (array) get_post_meta( $attachment_id, 'faces', true ); $this->hotspots = (array) get_post_meta( $attachment_id, 'hotspots', true ); return $file; } /** * Alters the crop location of the GD image editor class by detecting faces * and centering the crop around them * * @param array $payload Payload. * @param int $orig_w Original width. * @param int $orig_h Original height. * @param int $dest_w width. * @param int $dest_h height. * @param bool $crop Crop. * * @return array */ public function crop( $payload, $orig_w, $orig_h, $dest_w, $dest_h, $crop ) { $hotspots = array_filter( array_merge( $this->faces, $this->hotspots ) ); if ( ! $crop || empty( $hotspots ) ) { return $payload; } if ( is_array( $payload ) ) { list( $dest_x, $dest_y, $src_x, $src_y, $new_w, $new_h, $src_w, $src_h ) = $payload; } // Get faces area. $hotspot_src_x = $hotspot_src_y = PHP_INT_MAX; $hotspot_src_max_x = $hotspot_src_max_w = 0; $hotspot_src_max_y = $hotspot_src_max_h = 0; // Create bounding box. foreach ( $hotspots as $hotspot ) { $hotspot = array_map( 'absint', $hotspot ); // Left and top most x,y. if ( $hotspot_src_x > $hotspot['x'] ) { $hotspot_src_x = $hotspot['x']; } if ( $hotspot_src_y > $hotspot['y'] ) { $hotspot_src_y = $hotspot['y']; } // Right and bottom most x,y. if ( $hotspot_src_max_x < $hotspot['x'] ) { $hotspot_src_max_x = $hotspot['x']; } if ( $hotspot_src_max_y < $hotspot['y'] ) { $hotspot_src_max_y = $hotspot['y']; } } $hotspot_src_w = $hotspot_src_max_x - $hotspot_src_x; $hotspot_src_h = $hotspot_src_max_y - $hotspot_src_y; // Crop the largest possible portion of the original image that we can size to $dest_w x $dest_h. $aspect_ratio = $orig_w / $orig_h; // Preserve settings already filtered in. if ( null === $payload ) { $new_w = min( $dest_w, $orig_w ); $new_h = min( $dest_h, $orig_h ); if ( ! $new_w ) { $new_w = intval( $new_h * $aspect_ratio ); } if ( ! $new_h ) { $new_h = intval( $new_w / $aspect_ratio ); } } $size_ratio = max( $new_w / $orig_w, $new_h / $orig_h ); $crop_w = round( $new_w / $size_ratio ); $crop_h = round( $new_h / $size_ratio ); $src_x = floor( ( $orig_w - $crop_w ) / 2 ); $src_y = floor( ( $orig_h - $crop_h ) / 2 ); // Bounding box. if ( 0 == $src_x ) { $src_y = ( $hotspot_src_y + $hotspot_src_h / 2 ) - $crop_h / 2; $src_y = min( max( 0, $src_y ), $orig_h - $crop_h ); } if ( 0 == $src_y ) { $src_x = ( $hotspot_src_x + $hotspot_src_w / 2 ) - $crop_w / 2; $src_x = min( max( 0, $src_x ), $orig_w - $crop_w ); } return array( 0, 0, (int) $src_x, (int) $src_y, (int) $new_w, (int) $new_h, (int) $crop_w, (int) $crop_h ); } /** * Edit fields * * @param array $form_fields Form fields. * @param stdClass $attachment Attachment. * * @return mixed */ public function edit_fields( array $form_fields, $attachment ) { if ( ! wp_attachment_is_image( $attachment->ID ) ) { return $form_fields; } $faces = get_post_meta( $attachment->ID, 'faces', true ); $hotspots = get_post_meta( $attachment->ID, 'hotspots', true ); $data_atts = ''; if ( $faces ) { $data_atts .= ' data-faces="' . esc_attr( json_encode( $faces ) ) . '"'; } if ( $hotspots ) { $data_atts .= ' data-hotspots="' . esc_attr( json_encode( $hotspots ) ) . '"'; } $button = '
' . __( 'Thumb Previews', 'my-eyes-are-up-here' ) . '
'; foreach ( self::get_cropped_sizes() as $size => $atts ) { $src = wp_get_attachment_image_src( $attachment->ID, $size ); $button .= '
' . $size . '
'; } $button .= '
'; if ( $faces ) { $button .= sprintf( '', $attachment->ID, __( 'Forget found faces', 'my-eyes-are-up-here' ) ); } else { $button .= sprintf( '', $attachment->ID, __( 'Detect faces', 'my-eyes-are-up-here' ) ); } $button .= ''; $button .= sprintf( '

%s

', __( "Please note this is basic face detection and won't find everything. Use hotspots to highlight any that were missed.", 'my-eyes-are-up-here' ) ); $button .= '
'; if ( false && $faces ) { $button .= '

'; $button .= sprintf( __( '%d %s found, thumbnails regenerated to fit them into crop area.', 'my-eyes-are-up-here' ), count( $faces ), _n( 'face', 'faces', count( $faces ), 'my-eyes-are-up-here' ) ); $button .= '

'; } $button .= '
'; if ( $hotspots ) { $button .= sprintf( '', $attachment->ID, __( 'Edit hotspots', 'my-eyes-are-up-here' ) ); } else { $button .= sprintf( '', $attachment->ID, __( 'Add hotspots', 'my-eyes-are-up-here' ) ); } $button .= ''; $button .= sprintf( '

%s

', __( 'Manually add hotspots that you want to avoid cropping.', 'my-eyes-are-up-here' ) ); if ( false && $hotspots ) { $button .= '

'; $button .= sprintf( __( '%d %s found, thumbnails regenerated to fit them into crop area.', 'my-eyes-are-up-here' ), count( $hotspots ), _n( 'hotspot', 'hotspots', count( $hotspots ), 'my-eyes-are-up-here' ) ); $button .= '

'; } $button .= '

' . __( 'This plugin requires javascript to work', 'my-eyes-are-up-here' ) . '

'; $form_fields['face_detection'] = array( 'label' => __( 'Face detection', 'my-eyes-are-up-here' ), 'input' => 'html', 'html' => $button, ); return $form_fields; } /** * Get cropped sizes * * @return array */ protected static function get_cropped_sizes() { global $_wp_additional_image_sizes; $sizes = array(); $size_names = get_intermediate_image_sizes(); foreach ( $size_names as $size ) { if ( in_array( $size, array( 'thumbnail', 'medium', 'large' ) ) ) { $width = intval( get_option( $size . '_size_w' ) ); $height = intval( get_option( $size . '_size_h' ) ); $crop = get_option( $size . '_crop' ); } else if ( isset( $_wp_additional_image_sizes[ $size ] ) ) { $width = $_wp_additional_image_sizes[ $size ]['width']; $height = $_wp_additional_image_sizes[ $size ]['height']; $crop = $_wp_additional_image_sizes[ $size ]['crop']; } if ( $crop ) { $sizes[ $size ] = array( 'width' => $width, 'height' => $height, 'crop' => $crop, ); } } return $sizes; } } MEAUH_Attachment::init(); ================================================ FILE: languages/meauh.pot ================================================ # Copyright (C) 2016 interconnect/it # This file is distributed under the same license as the My Eyes Are Up Here package. msgid "" msgstr "" "Project-Id-Version: My Eyes Are Up Here 1.1.3\n" "Report-Msgid-Bugs-To: " "https://github.com/interconnectit/my-eyes-are-up-here/issues\n" "POT-Creation-Date: 2016-04-01 11:05:34+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "X-Generator: grunt-wp-i18n 0.5.4\n" #: includes/class-meauh-attachment.php:68 msgid "Unknown failure reason" msgstr "" #: includes/class-meauh-attachment.php:220 msgid "Thumb Previews" msgstr "" #: includes/class-meauh-attachment.php:234 msgid "Forget found faces" msgstr "" #: includes/class-meauh-attachment.php:239 msgid "Detect faces" msgstr "" #: includes/class-meauh-attachment.php:245 msgid "" "Please note this is basic face detection and won't find everything. Use " "hotspots to highlight any that were missed." msgstr "" #: includes/class-meauh-attachment.php:251 #: includes/class-meauh-attachment.php:281 msgid "%d %s found, thumbnails regenerated to fit them into crop area." msgstr "" #: includes/class-meauh-attachment.php:253 msgid "face" msgid_plural "faces" msgstr[0] "" msgstr[1] "" #: includes/class-meauh-attachment.php:265 msgid "Edit hotspots" msgstr "" #: includes/class-meauh-attachment.php:270 msgid "Add hotspots" msgstr "" #: includes/class-meauh-attachment.php:276 msgid "Manually add hotspots that you want to avoid cropping." msgstr "" #: includes/class-meauh-attachment.php:283 msgid "hotspot" msgid_plural "hotspots" msgstr[0] "" msgstr[1] "" #: includes/class-meauh-attachment.php:294 msgid "This plugin requires javascript to work" msgstr "" #: includes/class-meauh-attachment.php:298 msgid "Face detection" msgstr "" #. Plugin Name of the plugin/theme msgid "My Eyes Are Up Here" msgstr "" #. Plugin URI of the plugin/theme msgid "https://github.com/interconnectit/my-eyes-are-up-here" msgstr "" #. Description of the plugin/theme msgid "" "Detects faces during thumbnail cropping and moves the crop position " "accordingly." msgstr "" #. Author of the plugin/theme msgid "interconnect/it" msgstr "" #. Author URI of the plugin/theme msgid "http://interconnectit.com" msgstr "" ================================================ FILE: languages/my-eyes-are-up-here.pot ================================================ # Copyright (C) 2017 interconnect/it # This file is distributed under the same license as the My Eyes Are Up Here package. msgid "" msgstr "" "Project-Id-Version: My Eyes Are Up Here 1.1.8\n" "Report-Msgid-Bugs-To: " "https://github.com/interconnectit/my-eyes-are-up-here/issues\n" "POT-Creation-Date: 2017-05-15 10:38:31+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "X-Generator: grunt-wp-i18n 0.5.4\n" #: includes/class-meauh-attachment.php:68 msgid "Unknown failure reason" msgstr "" #: includes/class-meauh-attachment.php:220 msgid "Thumb Previews" msgstr "" #: includes/class-meauh-attachment.php:234 msgid "Forget found faces" msgstr "" #: includes/class-meauh-attachment.php:239 msgid "Detect faces" msgstr "" #: includes/class-meauh-attachment.php:245 msgid "" "Please note this is basic face detection and won't find everything. Use " "hotspots to highlight any that were missed." msgstr "" #: includes/class-meauh-attachment.php:252 #: includes/class-meauh-attachment.php:283 msgid "%d %s found, thumbnails regenerated to fit them into crop area." msgstr "" #: includes/class-meauh-attachment.php:255 msgid "face" msgid_plural "faces" msgstr[0] "" msgstr[1] "" #: includes/class-meauh-attachment.php:267 msgid "Edit hotspots" msgstr "" #: includes/class-meauh-attachment.php:272 msgid "Add hotspots" msgstr "" #: includes/class-meauh-attachment.php:278 msgid "Manually add hotspots that you want to avoid cropping." msgstr "" #: includes/class-meauh-attachment.php:286 msgid "hotspot" msgid_plural "hotspots" msgstr[0] "" msgstr[1] "" #: includes/class-meauh-attachment.php:297 msgid "This plugin requires javascript to work" msgstr "" #: includes/class-meauh-attachment.php:301 msgid "Face detection" msgstr "" #. Plugin Name of the plugin/theme msgid "My Eyes Are Up Here" msgstr "" #. Plugin URI of the plugin/theme msgid "https://github.com/interconnectit/my-eyes-are-up-here" msgstr "" #. Description of the plugin/theme msgid "" "Detects faces during thumbnail cropping and moves the crop position " "accordingly." msgstr "" #. Author of the plugin/theme msgid "interconnect/it" msgstr "" #. Author URI of the plugin/theme msgid "http://interconnectit.com" msgstr "" ================================================ FILE: my-eyes-are-up-here.php ================================================ includes(); $this->init_hooks(); } /** * Determine request type * * @param string $type Request type. * * @return bool */ public function is_request( $type ) { switch ( $type ) { case self::REQUEST_ADMIN: return is_admin(); case self::REQUEST_AJAX: return defined( 'DOING_AJAX' ); } } /** * Get plugin path * * @return string */ public function plugin_path() { return untrailingslashit( plugin_dir_path( __FILE__ ) ); } /** * Get plugin URL * * @return string */ public function plugin_url() { return untrailingslashit( plugins_url( '/', __FILE__ ) ); } /** * Get ajax URL * * @return string */ public function ajax_url() { return admin_url( 'admin-ajax.php', 'relative' ); } /** * Load localisation */ public function localisation() { $locale = apply_filters( 'plugin_locale', get_locale(), 'my-eyes-are-up-here' ); load_textdomain( 'my-eyes-are-up-here', WP_LANG_DIR . '/my-eyes-are-up-here/my-eyes-are-up-here-' . $locale . '.mo' ); load_plugin_textdomain( 'my-eyes-are-up-here', false, plugin_basename( dirname( __FILE__ ) ) . '/languages' ); } /** * Includes */ protected function includes() { require_once 'includes/class-meauh-ajax.php'; require_once 'includes/class-meauh-attachment.php'; if ( $this->is_request( self::REQUEST_ADMIN ) ) { require_once 'includes/class-meauh-admin.php'; } } /** * Init hooks */ protected function init_hooks() { add_action( 'init', array( $this, 'localisation' ), 0 ); } } /** * Get instance * * @return MyEyesAreUpHere */ function meauh() { return MyEyesAreUpHere::instance(); } // Global for backwards compatibility. $GLOBALS['meauh'] = meauh(); ================================================ FILE: package.json ================================================ { "name": "my-eyes-are-up-here", "version": "1.1.9", "author": "interconnect/it ", "homepage": "https://interconnectit.com", "private": true, "repository": { "type": "git", "url": "git://github.com/interconnectit/my-eyes-are-up-here.git" }, "bugs": { "url": "https://github.com/interconnectit/my-eyes-are-up-here/issues" }, "licenses": [ { "type": "GPLv2", "url": "http://www.gnu.org/licenses/gpl-2.0.html" } ], "scripts": { "postinstall": "bower install && grunt build" }, "devDependencies": { "bower": "^1.7.1", "grunt": "^0.4.5", "grunt-autoprefixer": "^3.0.3", "grunt-contrib-cssmin": "^0.14.0", "grunt-contrib-jshint": "^0.11.3", "grunt-contrib-uglify": "^0.11.0", "grunt-wp-i18n": "^0.5.4", "load-grunt-tasks": "^3.3.0", "time-grunt": "^1.2.2" } } ================================================ FILE: readme.txt ================================================ === My Eyes Are Up Here === Contributors: interconnectit, sanchothefat, spectacula, AndyWalmsley Donate link: https://myeyesareuphere.interconnectit.com/donate/ Tags: thumbnails, image editing, image, featured image Requires at least: 3.8.1 Tested up to: 4.7.4 Stable tag: 1.1.9 License: GPLv2 License URI: http://www.gnu.org/licenses/gpl-2.0.html My Eyes Are Up Here helps you control how WordPress generates thumbnails. == Description == = What is it? = A fantastic new plugin that helps you control how WordPress generates thumbnails. = Why use it? = When WordPress automatically generates thumbnails, it sometimes doesn't crop them in a way that is suitable for the image you've uploaded. If your image isn't the correct format, and let's face it, you never know what images people are uploading - you'll run the risk of a badly cropped image. Not good. If you have a full portrait image of a person that you've uploaded, but you need the image to appear landscape, you're in trouble! WordPress will centre the image so that you end up with person's crotch. Not good. Or let's say you have a landscape image, with a person's face on the right hand side, but you need it to display in a square thumbnail. You'll end up with half a face as WordPress centres the image. = What does the plugin do? = You can control how you want your WordPress thumbnails to appear on your website. Regardless of the image format you upload, you can either use the automatic face detector or if you want even more control, you can manually add hotspots. = How do I use it? = Navigate to your media library then click on the image you want to edit. Use the detect faces or edit hotspots option to edit your image. You'll see thumbnail previews when you've applied these edits, when you're happy hit update. Simple. == Installation == = The install = 1. You can install the plugin using the auto-install tool from the WordPress back-end. 2. To manually install, upload the folder `/myeyesareuphere/` to `/wp-content/plugins/` directory. 3. Activate the plugin through the 'Plugins' menu in WordPress == Usage == 1. Once the plugin is activated, navigate to your 'Media Library'. 2. Click on the image you want to edit to bring up your 'Edit Media' options. 3. You should now be able to see extra image editing options, below the 'Description' box. 4. By clicking 'Detect faces' or 'Add hotspots' you can now start to edit your image thumbnails. 5. If you click the 'Detect faces' button, it will centre the crop using an average of all the faces it finds. 5. Please note this is basic face detection and won't find everything. 6. You can click and create 1 or several hotspots to centre the crop of your thumbnails, if 'Detect faces' doesn't work. 7. If you're happy with your 'Thumb Previews' hit save, and you're done. Simple. == Frequently Asked Questions == = What happens when there are multiple hotspots/faces detected? = This will crop the image to get as many hotspots in the thumbnail as possible or crop around the center of the hotspots if not. = How do I report a problem? = You can email us at cases@interconnectit.fogbugz.com with "My Eyes Are Up Here" in the subject and the following information: 1. What browser and version is this problem occurring with? 2. What WordPress version are you using? 3. What version of My Eyes Are Up Here are you using? 4. Are there any errors in the javascript console? * Chrome and Firefox: ctrl + shift + j (Win) or alt + cmd + j (Mac) * Internet Explorer: F12 and click on 'Script' then 'Console' 5. What are the steps you used to produce this problem? == Screenshots == 1. Default WordPress cropping where the thumbnail is cropped to the centre of the image. 2. Thumbnail after My Eyes Are Up Here has detected any faces in the image. 3. How WordPress crops the image without My Eyes Are Up Here. 4. How the image appears once My Eyes Are Up Here has been installed and applied. == Changelog == = 1.1.9 = * Fix the js error for WP Customizer = 1.1.8 = * Remove deprecated jQuery methods = 1.1.7 = * Remove PHP warnings * Add composer support = 1.1.6 = * Prevent hotspots from being placed outside edges of image = 1.1.5 = * AS3CF compatibility = 1.1.4 = * Fix translation file = 1.1.3 = * Fix text domain = 1.1.2 = * Add translation file = 1.1.1 = * Better ajax validation * Fix regenerate thumbnail issue = 1.1.0 = * Complete plugin refactoring = 1.0.2 = * Fixed for versions 4.4.* = 1.0.1 = * Now only runs on image attachments = 1.0 = * Release version = 0.4 = * Bugfixes, play nicely with other plugins/themes that modify image sizes = 0.3 = * Hotspots! = 0.2: = * jQuery option for speed == Upgrade Notice == = 1.0.1 = * No longer runs on non image media = 1.0 = * Release version = 0.4 = * Lot's of bugfixes