├── __init__.py ├── imagekit_cropper ├── __init__.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── generateimages_cropped.py ├── static │ └── imagekit_cropper │ │ ├── jcrop │ │ ├── css │ │ │ ├── Jcrop.gif │ │ │ ├── jquery.Jcrop.min.css │ │ │ └── jquery.Jcrop.css │ │ └── js │ │ │ ├── jquery.Jcrop.min.js │ │ │ ├── jquery.color.js │ │ │ └── jquery.Jcrop.js │ │ ├── css │ │ └── imagecrop.css │ │ └── js │ │ └── imagecrop.js ├── widgets.py ├── specs.py ├── registry.py ├── processors.py ├── fields.py └── utils.py ├── CHANGELOG.rst ├── setup.cfg ├── docs ├── .DS_Store └── screenshots │ └── crop_screenshot.png ├── AUTHORS.rst ├── .gitignore ├── MANIFEST.in ├── setup.py ├── LICENSE.txt └── README.md /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /imagekit_cropper/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /imagekit_cropper/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /imagekit_cropper/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /docs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninapavlich/django-imagekit-cropper/HEAD/docs/.DS_Store -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | AUTHORS 2 | ======= 3 | 4 | 5 | Created By 6 | ---------- 7 | #. `ninapavlich `_ -------------------------------------------------------------------------------- /docs/screenshots/crop_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninapavlich/django-imagekit-cropper/HEAD/docs/screenshots/crop_screenshot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.pyo 4 | .env 5 | node_modules/ 6 | dist 7 | *.egg 8 | *.egg-info 9 | _mailinglist 10 | .tox 11 | venv 12 | .sass-cache/ 13 | build/ -------------------------------------------------------------------------------- /imagekit_cropper/static/imagekit_cropper/jcrop/css/Jcrop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninapavlich/django-imagekit-cropper/HEAD/imagekit_cropper/static/imagekit_cropper/jcrop/css/Jcrop.gif -------------------------------------------------------------------------------- /imagekit_cropper/static/imagekit_cropper/css/imagecrop.css: -------------------------------------------------------------------------------- 1 | .image-crop-data .upscale{ 2 | max-width:260px; 3 | font-weight:bold; 4 | } 5 | .image-crop-data .upscale.warning{ 6 | color:#ab2b2b; 7 | } 8 | .jcrop-holder{ 9 | background-color:#eee !important; 10 | } -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE.txt 3 | include imagekit_cropper/static/imagekit_cropper/css/*.css 4 | include imagekit_cropper/static/imagekit_cropper/js/*.js 5 | include imagekit_cropper/static/imagekit_cropper/js/vendor/*.js 6 | include imagekit_cropper/static/imagekit_cropper/jcrop/css/*.css 7 | include imagekit_cropper/static/imagekit_cropper/jcrop/css/*.gif 8 | include imagekit_cropper/static/imagekit_cropper/jcrop/js/*.js 9 | include imagekit_cropper/management/*.py 10 | include imagekit_cropper/management/commands/*.py 11 | recursive-include imagekit_cropper/ *.py *.css *.js *.html -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='django-imagekit-cropper', 5 | version='1.18', 6 | author='Nina Pavlich', 7 | author_email='nina@ninalp.com', 8 | url='https://github.com/ninapavlich/django-imagekit-cropper', 9 | license="MIT", 10 | description='Allow users to manually specify image variant crops', 11 | keywords=['libraries', 'web development', 'cms', 'django', 'django-grappelli'], 12 | include_package_data=True, 13 | packages=['imagekit_cropper'], 14 | 15 | classifiers=[ 16 | 'Development Status :: 4 - Beta', 17 | 'Environment :: Web Environment', 18 | 'Framework :: Django', 19 | 'Intended Audience :: Developers', 20 | 'License :: OSI Approved :: MIT License', 21 | 'Natural Language :: English', 22 | 'Operating System :: OS Independent', 23 | 'Programming Language :: Python' 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nina Pavlich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /imagekit_cropper/widgets.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin import widgets 2 | from django.contrib.admin.utils import help_text_for_field 3 | from django.utils.safestring import mark_safe 4 | 5 | class ImageCropWidget(widgets.AdminTextInputWidget): 6 | image_model = None 7 | 8 | class Media: 9 | css = { 10 | 'all': ('imagekit_cropper/jcrop/css/jquery.Jcrop.css', 'imagekit_cropper/css/imagecrop.css',) 11 | } 12 | js = ('imagekit_cropper/js/vendor/jquery.js','imagekit_cropper/js/imagecrop.js','imagekit_cropper/jcrop/js/jquery.Jcrop.js') 13 | 14 | def __init__(self, properties, help_text='', *args, **kwargs): 15 | self.properties = properties 16 | self.help_text = help_text 17 | 18 | return super(ImageCropWidget, self).__init__(*args, **kwargs) 19 | 20 | 21 | 22 | def get_crop_containers(self): 23 | properties = self.properties 24 | if 'aspect_ratio' in self.properties: 25 | return '
\ 27 | '%(self.properties['min_width'], self.properties['min_height'], 28 | self.properties['aspect_ratio'], self.properties['resize_method'], 29 | self.properties['source'], self.properties['upscale']) 30 | 31 | else: 32 | return '
\ 34 | '%(self.properties['width'], 35 | self.properties['height'], self.properties['resize_method'], 36 | self.properties['source'], self.properties['upscale']) 37 | 38 | def render(self, name, value, attrs=None): 39 | rendered = super(ImageCropWidget, self).render(name, value, attrs) 40 | return mark_safe('
%s%s

%s

\ 42 |
'%(rendered, self.get_crop_containers(), self.help_text)) -------------------------------------------------------------------------------- /imagekit_cropper/static/imagekit_cropper/jcrop/css/jquery.Jcrop.min.css: -------------------------------------------------------------------------------- 1 | /* jquery.Jcrop.min.css v0.9.12 (build:20130126) */ 2 | .jcrop-holder{direction:ltr;text-align:left;} 3 | .jcrop-vline,.jcrop-hline{background:#FFF url(Jcrop.gif);font-size:0;position:absolute;} 4 | .jcrop-vline{height:100%;width:1px!important;} 5 | .jcrop-vline.right{right:0;} 6 | .jcrop-hline{height:1px!important;width:100%;} 7 | .jcrop-hline.bottom{bottom:0;} 8 | .jcrop-tracker{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;-webkit-user-select:none;height:100%;width:100%;} 9 | .jcrop-handle{background-color:#333;border:1px #EEE solid;font-size:1px;height:7px;width:7px;} 10 | .jcrop-handle.ord-n{left:50%;margin-left:-4px;margin-top:-4px;top:0;} 11 | .jcrop-handle.ord-s{bottom:0;left:50%;margin-bottom:-4px;margin-left:-4px;} 12 | .jcrop-handle.ord-e{margin-right:-4px;margin-top:-4px;right:0;top:50%;} 13 | .jcrop-handle.ord-w{left:0;margin-left:-4px;margin-top:-4px;top:50%;} 14 | .jcrop-handle.ord-nw{left:0;margin-left:-4px;margin-top:-4px;top:0;} 15 | .jcrop-handle.ord-ne{margin-right:-4px;margin-top:-4px;right:0;top:0;} 16 | .jcrop-handle.ord-se{bottom:0;margin-bottom:-4px;margin-right:-4px;right:0;} 17 | .jcrop-handle.ord-sw{bottom:0;left:0;margin-bottom:-4px;margin-left:-4px;} 18 | .jcrop-dragbar.ord-n,.jcrop-dragbar.ord-s{height:7px;width:100%;} 19 | .jcrop-dragbar.ord-e,.jcrop-dragbar.ord-w{height:100%;width:7px;} 20 | .jcrop-dragbar.ord-n{margin-top:-4px;} 21 | .jcrop-dragbar.ord-s{bottom:0;margin-bottom:-4px;} 22 | .jcrop-dragbar.ord-e{margin-right:-4px;right:0;} 23 | .jcrop-dragbar.ord-w{margin-left:-4px;} 24 | .jcrop-light .jcrop-vline,.jcrop-light .jcrop-hline{background:#FFF;filter:alpha(opacity=70)!important;opacity:.70!important;} 25 | .jcrop-light .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#000;border-color:#FFF;border-radius:3px;} 26 | .jcrop-dark .jcrop-vline,.jcrop-dark .jcrop-hline{background:#000;filter:alpha(opacity=70)!important;opacity:.7!important;} 27 | .jcrop-dark .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#FFF;border-color:#000;border-radius:3px;} 28 | .solid-line .jcrop-vline,.solid-line .jcrop-hline{background:#FFF;} 29 | .jcrop-holder img,img.jcrop-preview{max-width:none;} 30 | -------------------------------------------------------------------------------- /imagekit_cropper/specs.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | from django.conf import settings 4 | from django.db import models 5 | from django.db.models.signals import post_init, post_save 6 | from django.dispatch import Signal 7 | from django.utils.functional import wraps 8 | 9 | from imagekit import hashers 10 | from imagekit import ImageSpec, register 11 | from imagekit.utils import get_field_info, open_image, process_image 12 | from imagekit.cachefiles import ImageCacheFile 13 | from imagekit.exceptions import MissingSource 14 | 15 | 16 | 17 | 18 | class InstanceSpec(ImageSpec): 19 | 20 | 21 | def __init__(self, source, specs): 22 | 23 | instance, attname = get_field_info(source) 24 | self.specs = specs 25 | 26 | #Apply specs to instance 27 | for attr_name in specs: 28 | setattr(self, attr_name, specs[attr_name]) 29 | 30 | self.source = source 31 | self.instance = instance 32 | self.attname = attname 33 | 34 | self.init_processors() 35 | 36 | super(InstanceSpec, self).__init__(source) 37 | 38 | def get_format(self): 39 | return self.format 40 | 41 | def init_processors(self): 42 | #Pass instance reference to image processors 43 | for instance_processor in self.processors: 44 | instance_processor.image_instance = self.instance 45 | 46 | def get_hash(self): 47 | keys = [ 48 | self.source.name, 49 | self.get_format(), 50 | self.options, 51 | self.autoconvert 52 | ] 53 | 54 | 55 | #Use custom hashing function 56 | for processor in self.processors: 57 | if hasattr(processor, 'get_hash'): 58 | keys.append(processor.get_hash()) 59 | else: 60 | keys.append(processor) 61 | 62 | # print 'keys: %s'%(keys) 63 | pickled = hashers.pickle(keys) 64 | return pickled 65 | 66 | 67 | 68 | class InstanceFormatSpec(InstanceSpec): 69 | 70 | 71 | def __init__(self, source, specs): 72 | super(InstanceFormatSpec, self).__init__(source, specs) 73 | 74 | self.update_format() 75 | 76 | 77 | #HACK TO UPDATE FORMAT BASED ON MODEL VALUE 78 | def get_format(self): 79 | if hasattr(self, 'instance') and hasattr(self, 'format_field'): 80 | return getattr(self.instance, self.format_field) 81 | return self.format 82 | 83 | def update_format(self): 84 | self.format = self.get_format() 85 | 86 | def generate(self): 87 | self.update_format() 88 | return super(InstanceFormatSpec, self).generate() 89 | 90 | def get_hash(self): 91 | self.update_format() 92 | return super(InstanceFormatSpec, self).get_hash() 93 | 94 | 95 | 96 | 97 | register.generator('imagekit_cropper:instance_spec', InstanceSpec) 98 | register.generator('imagekit_cropper:instance_format_spec', InstanceFormatSpec) 99 | 100 | 101 | -------------------------------------------------------------------------------- /imagekit_cropper/registry.py: -------------------------------------------------------------------------------- 1 | # from imagekit import hashers 2 | # from imagekit import ImageSpec, register 3 | # from imagekit.models import ProcessedImageField 4 | 5 | # from imagekit.registry import generator_registry, cachefile_registry 6 | # from imagekit.specs.sourcegroups import ImageFieldSourceGroup, ModelSignalRouter, ik_model_receiver 7 | from imagekit.utils import call_strategy_method 8 | # from imagekit.cachefiles import ImageCacheFile 9 | # from imagekit.exceptions import MissingSource 10 | from imagekit.signals import source_saved 11 | from imagekit.registry import cachefile_registry, generator_registry 12 | 13 | spec_data_field_hash = {} 14 | 15 | class InstanceSourceGroupRegistry(object): 16 | """ 17 | EXTENDS InstanceSourceGroupRegistry 18 | 19 | """ 20 | _signals = { 21 | source_saved: 'on_source_saved', 22 | } 23 | 24 | def __init__(self): 25 | self._source_groups = {} 26 | for signal in self._signals.keys(): 27 | signal.connect(self.source_group_receiver) 28 | 29 | def register(self, generator_id, source_group, data): 30 | 31 | from imagekit.specs.sourcegroups import SourceGroupFilesGenerator 32 | generator_ids = self._source_groups.setdefault(source_group, set()) 33 | generator_ids.add(generator_id) 34 | spec_data_field_hash[generator_id] = data 35 | cachefile_registry.register(generator_id, 36 | SourceGroupFilesGenerator(source_group, generator_id)) 37 | 38 | def unregister(self, generator_id, source_group): 39 | from imagekit.specs.sourcegroups import SourceGroupFilesGenerator 40 | generator_ids = self._source_groups.setdefault(source_group, set()) 41 | if generator_id in generator_ids: 42 | generator_ids.remove(generator_id) 43 | cachefile_registry.unregister(generator_id, 44 | SourceGroupFilesGenerator(source_group, generator_id)) 45 | 46 | def source_group_receiver(self, sender, source, signal, **kwargs): 47 | """ 48 | Relay source group signals to the appropriate spec strategy. 49 | 50 | """ 51 | 52 | from imagekit.cachefiles import ImageCacheFile 53 | source_group = sender 54 | 55 | # Ignore signals from unregistered groups. 56 | if source_group not in self._source_groups: 57 | return 58 | 59 | 60 | #OVERRIDE HERE -- pass specs into generator object 61 | specs = [generator_registry.get(id, source=source, specs=spec_data_field_hash[id]) for id in 62 | self._source_groups[source_group]] 63 | callback_name = self._signals[signal] 64 | #END OVERRIDE 65 | 66 | for spec in specs: 67 | file = ImageCacheFile(spec) 68 | call_strategy_method(file, callback_name) 69 | 70 | instance_source_group_registry = InstanceSourceGroupRegistry() -------------------------------------------------------------------------------- /imagekit_cropper/management/commands/generateimages_cropped.py: -------------------------------------------------------------------------------- 1 | import re 2 | import traceback 3 | 4 | from django.core.management.base import BaseCommand 5 | 6 | from imagekit.registry import generator_registry, cachefile_registry 7 | from imagekit.utils import call_strategy_method 8 | from imagekit.cachefiles import ImageCacheFile 9 | 10 | from imagekit_cropper.registry import spec_data_field_hash 11 | 12 | 13 | class Command(BaseCommand): 14 | help = ("""Generate files for the specified image generators (or all of them if 15 | none was provided). Simple, glob-like wildcards are allowed, with * 16 | matching all characters within a segment, and ** matching across 17 | segments. (Segments are separated with colons.) So, for example, 18 | "a:*:c" will match "a:b:c", but not "a:b:x:c", whereas "a:**:c" will 19 | match both. Subsegments are always matched, so "a" will match "a" as 20 | well as "a:b" and "a:b:c".""") 21 | args = '[generator_ids]' 22 | 23 | def add_arguments(self, parser): 24 | parser.add_argument('generator_id', nargs='*', help=':: for model specs') 25 | def handle(self, *args, **options): 26 | generators = generator_registry.get_ids() 27 | 28 | generator_ids = options['generator_id'] if 'generator_id' in options else args 29 | if generator_ids: 30 | patterns = self.compile_patterns(generator_ids) 31 | generators = (id for id in generators if any(p.match(id) for p in patterns)) 32 | 33 | for generator_id in generators: 34 | self.stdout.write('Validating generator: %s\n' % generator_id) 35 | 36 | 37 | try: 38 | for image_file in cachefile_registry.get(generator_id): 39 | if image_file.name: 40 | self.stdout.write(' %s\n' % image_file.name) 41 | try: 42 | image_file.generate() 43 | except Exception as err: 44 | self.stdout.write('\tFailed %s\n' % (err)) 45 | except: 46 | items = cachefile_registry._cachefiles.items() 47 | 48 | for k, v in items: 49 | 50 | 51 | if generator_id in v: 52 | #k is SourceGroupFilesGenerator 53 | 54 | 55 | model_class = k.source_group.model_class 56 | image_field = k.source_group.image_field 57 | all_objects = model_class.objects.all() 58 | item_count = len(all_objects) 59 | count = 0 60 | 61 | for instance in all_objects: 62 | count += 1 63 | 64 | print 65 | try: 66 | source = getattr(instance, image_field) 67 | specs = spec_data_field_hash[generator_id] 68 | 69 | spec = generator_registry.get(generator_id, source=source, specs=specs) 70 | 71 | file = ImageCacheFile(spec) 72 | self.stdout.write(' [%s of %s] - %s\n' % (count, item_count, file)) 73 | call_strategy_method(file, 'on_source_saved') 74 | 75 | except Exception: 76 | 77 | self.stdout.write('ERROR: %s\n' % (traceback.format_exc())) 78 | 79 | 80 | def compile_patterns(self, generator_ids): 81 | return [self.compile_pattern(id) for id in generator_ids] 82 | 83 | def compile_pattern(self, generator_id): 84 | parts = re.split(r'(\*{1,2})', generator_id) 85 | pattern = '' 86 | for part in parts: 87 | if part == '*': 88 | pattern += '[^:]*' 89 | elif part == '**': 90 | pattern += '.*' 91 | else: 92 | pattern += re.escape(part) 93 | return re.compile('^%s(:.*)?$' % pattern) 94 | -------------------------------------------------------------------------------- /imagekit_cropper/static/imagekit_cropper/jcrop/css/jquery.Jcrop.css: -------------------------------------------------------------------------------- 1 | /* jquery.Jcrop.css v0.9.12 - MIT License */ 2 | /* 3 | The outer-most container in a typical Jcrop instance 4 | If you are having difficulty with formatting related to styles 5 | on a parent element, place any fixes here or in a like selector 6 | 7 | You can also style this element if you want to add a border, etc 8 | A better method for styling can be seen below with .jcrop-light 9 | (Add a class to the holder and style elements for that extended class) 10 | */ 11 | .jcrop-holder { 12 | direction: ltr; 13 | text-align: left; 14 | } 15 | /* Selection Border */ 16 | .jcrop-vline, 17 | .jcrop-hline { 18 | background: #ffffff url("Jcrop.gif"); 19 | font-size: 0; 20 | position: absolute; 21 | } 22 | .jcrop-vline { 23 | height: 100%; 24 | width: 1px !important; 25 | } 26 | .jcrop-vline.right { 27 | right: 0; 28 | } 29 | .jcrop-hline { 30 | height: 1px !important; 31 | width: 100%; 32 | } 33 | .jcrop-hline.bottom { 34 | bottom: 0; 35 | } 36 | /* Invisible click targets */ 37 | .jcrop-tracker { 38 | height: 100%; 39 | width: 100%; 40 | /* "turn off" link highlight */ 41 | -webkit-tap-highlight-color: transparent; 42 | /* disable callout, image save panel */ 43 | -webkit-touch-callout: none; 44 | /* disable cut copy paste */ 45 | -webkit-user-select: none; 46 | } 47 | /* Selection Handles */ 48 | .jcrop-handle { 49 | background-color: #333333; 50 | border: 1px #eeeeee solid; 51 | width: 7px; 52 | height: 7px; 53 | font-size: 1px; 54 | } 55 | .jcrop-handle.ord-n { 56 | left: 50%; 57 | margin-left: -4px; 58 | margin-top: -4px; 59 | top: 0; 60 | } 61 | .jcrop-handle.ord-s { 62 | bottom: 0; 63 | left: 50%; 64 | margin-bottom: -4px; 65 | margin-left: -4px; 66 | } 67 | .jcrop-handle.ord-e { 68 | margin-right: -4px; 69 | margin-top: -4px; 70 | right: 0; 71 | top: 50%; 72 | } 73 | .jcrop-handle.ord-w { 74 | left: 0; 75 | margin-left: -4px; 76 | margin-top: -4px; 77 | top: 50%; 78 | } 79 | .jcrop-handle.ord-nw { 80 | left: 0; 81 | margin-left: -4px; 82 | margin-top: -4px; 83 | top: 0; 84 | } 85 | .jcrop-handle.ord-ne { 86 | margin-right: -4px; 87 | margin-top: -4px; 88 | right: 0; 89 | top: 0; 90 | } 91 | .jcrop-handle.ord-se { 92 | bottom: 0; 93 | margin-bottom: -4px; 94 | margin-right: -4px; 95 | right: 0; 96 | } 97 | .jcrop-handle.ord-sw { 98 | bottom: 0; 99 | left: 0; 100 | margin-bottom: -4px; 101 | margin-left: -4px; 102 | } 103 | /* Dragbars */ 104 | .jcrop-dragbar.ord-n, 105 | .jcrop-dragbar.ord-s { 106 | height: 7px; 107 | width: 100%; 108 | } 109 | .jcrop-dragbar.ord-e, 110 | .jcrop-dragbar.ord-w { 111 | height: 100%; 112 | width: 7px; 113 | } 114 | .jcrop-dragbar.ord-n { 115 | margin-top: -4px; 116 | } 117 | .jcrop-dragbar.ord-s { 118 | bottom: 0; 119 | margin-bottom: -4px; 120 | } 121 | .jcrop-dragbar.ord-e { 122 | margin-right: -4px; 123 | right: 0; 124 | } 125 | .jcrop-dragbar.ord-w { 126 | margin-left: -4px; 127 | } 128 | /* The "jcrop-light" class/extension */ 129 | .jcrop-light .jcrop-vline, 130 | .jcrop-light .jcrop-hline { 131 | background: #ffffff; 132 | filter: alpha(opacity=70) !important; 133 | opacity: .70!important; 134 | } 135 | .jcrop-light .jcrop-handle { 136 | -moz-border-radius: 3px; 137 | -webkit-border-radius: 3px; 138 | background-color: #000000; 139 | border-color: #ffffff; 140 | border-radius: 3px; 141 | } 142 | /* The "jcrop-dark" class/extension */ 143 | .jcrop-dark .jcrop-vline, 144 | .jcrop-dark .jcrop-hline { 145 | background: #000000; 146 | filter: alpha(opacity=70) !important; 147 | opacity: 0.7 !important; 148 | } 149 | .jcrop-dark .jcrop-handle { 150 | -moz-border-radius: 3px; 151 | -webkit-border-radius: 3px; 152 | background-color: #ffffff; 153 | border-color: #000000; 154 | border-radius: 3px; 155 | } 156 | /* Simple macro to turn off the antlines */ 157 | .solid-line .jcrop-vline, 158 | .solid-line .jcrop-hline { 159 | background: #ffffff; 160 | } 161 | /* Fix for twitter bootstrap et al. */ 162 | .jcrop-holder img, 163 | img.jcrop-preview { 164 | max-width: none; 165 | } 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-imagekit-cropper 2 | A library to enhance django-imagekit which allows you to specify your image variant crops: 3 | 4 | ![CKEditor Dialog](https://raw.github.com/ninapavlich/django-imagekit-cropper/master/docs/screenshots/crop_screenshot.png) 5 | 6 | **InstanceSpecField** This is a class that extends ImageSpecField which passes the model instance to 7 | the processors so that you can access other model fields to process the image. 8 | 9 | **InstanceFormatSpecField** This is a class that extends InstanceSpecField and can dynamically choose the image format (e.g. JPEG, PNG, GIF). PNGs have a much larger file size than JPEGs, but sometimes it's worth the filesize; this allows the admins to control on a per-image basis. Just pass a reference to the model field that returns a PIL-compatible file format. 10 | 11 | **ImageCropField** This is a custom model field for setting the image crop. This uses a custom 12 | widget to allow admins to visually crop the image. 13 | 14 | **PositionCrop** This a custom processor which recieves the model instance and crops the image 15 | using the image source and the value of the image crop field. 16 | 17 | **FormatProcessor** This is a custom processor which implements resizing and outputs to a dynamic format. 18 | 19 | **PositionAndFormatCrop** This processor extends PositionCrop and also implements dynamic format. 20 | 21 | **WARNING:** This library is in very early alpha stages. I have only tested this on version django-imagekit==3.2.5 22 | 23 | ## Example Usage 24 | ``` 25 | $ pip install django-imagekit-cropper 26 | ``` 27 | 28 | ``` 29 | #settings.py 30 | 31 | INSTALLED_APPS = ( 32 | ... 33 | imagekit_cropper, 34 | ... 35 | ) 36 | ``` 37 | 38 | 39 | ```python 40 | #models.py 41 | 42 | from imagekit_cropper.fields import ImageCropField, InstanceSpecField, InstanceFormatSpecField 43 | from imagekit_cropper.processors import PositionCrop, PositionAndFormatCrop, FormatProcessor 44 | 45 | image = models.ImageField(blank=True, null=True) 46 | 47 | #Example 1 - BASIC CROP FIELD 48 | width_1200_wide_crop_properties = { 49 | 'source':'image', 50 | 'crop_field':'width_1200_wide_crop', 51 | 'resize_method':'fill', 52 | 'width':1200, 53 | 'height':750, 54 | 'upscale':True 55 | } 56 | width_1200_wide_crop = ImageCropField(null=True, blank=True, 57 | properties=width_1200_wide_crop_properties) 58 | 59 | width_1200_wide = InstanceSpecField( 60 | source=width_1200_wide_crop_properties['source'], 61 | options={'quality': 85}, 62 | processors=[PositionCrop(width_1200_wide_crop_properties)]) 63 | 64 | #Example 2 - DYNAMIC FORMAT FIELD 65 | 66 | 67 | width_1200_crop_properties = { 68 | 'source':'image', 69 | 'format_field':'get_format', 70 | 'resize_method':'fit', 71 | 'width':1200, 72 | 'height':None, 73 | 'upscale':False 74 | } 75 | width_1200 = InstanceFormatSpecField( 76 | source=width_1200_crop_properties['source'], 77 | format_field=width_1200_crop_properties['format_field'], 78 | options={'quality': 95}, 79 | processors=[FormatProcessor(width_1200_crop_properties)]) 80 | 81 | use_png = models.BooleanField( default = False, 82 | verbose_name='Use .PNG (instead of .JPG)') 83 | 84 | @property 85 | def get_format(self): 86 | if self.use_png: 87 | return 'PNG' 88 | return 'JPEG' 89 | 90 | 91 | #Example 3 - TWO SPECS USING THE SAME CROP 92 | 93 | square_crop_properties = { 94 | 'source':'image', 95 | 'crop_field':'square_crop', 96 | 'format_field':'get_format', 97 | 'resize_method':'fill', 98 | 'aspect_ratio':1, 99 | 'min_width':400, 100 | 'min_height':400, 101 | 'upscale':False 102 | } 103 | square_crop = ImageCropField(null=True, blank=True, properties=square_crop_properties) 104 | 105 | square_200_crop_properties = copy.copy(square_crop_properties) 106 | square_200_crop_properties['width'] = 200 107 | square_200_crop_properties['height'] = 200 108 | 109 | square_200 = InstanceSpecField( 110 | format='PNG', 111 | source=square_200_crop_properties['source'], 112 | options={'quality': 85}, 113 | processors=[PositionCrop(square_200_crop_properties)] 114 | ) 115 | 116 | square_400_crop_properties = copy.copy(square_crop_properties) 117 | square_400_crop_properties['width'] = 400 118 | square_400_crop_properties['height'] = 400 119 | 120 | square_400 = InstanceFormatSpecField( 121 | source=square_400_crop_properties['source'], 122 | format_field=square_400_crop_properties['format_field'], 123 | options={'quality': 85}, 124 | processors=[PositionAndFormatCrop(square_400_crop_properties)] 125 | ) 126 | 127 | ``` 128 | 129 | ```python 130 | #admin.py 131 | 132 | from .forms import ImageAdminForm 133 | 134 | class ImageAdmin(models.ModelAdmin): 135 | form = ImageAdminForm 136 | 137 | ``` 138 | 139 | ```python 140 | #forms.py 141 | 142 | from django import forms 143 | from imagekit_cropper.widgets import ImageCropWidget 144 | from .models import Image 145 | 146 | 147 | class ImageAdminForm(forms.ModelForm): 148 | square_150_crop = forms.CharField(widget=ImageCropWidget(properties=Image.square_150_crop_properties, help_text=Image.help['square_150_crop'])) 149 | 150 | class Meta: 151 | model = Image 152 | 153 | ``` 154 | 155 | To generate images, you'll need to use this modified version of the generateimages command: 156 | ``` 157 | $ python manage.py generateimages_cropped 158 | ``` 159 | 160 | -------------------------------------------------------------------------------- /imagekit_cropper/processors.py: -------------------------------------------------------------------------------- 1 | from imagekit.processors import ResizeToFill, ResizeToFit 2 | 3 | from pilkit.processors.resize import ResizeCanvas 4 | 5 | 6 | class BaseInstanceProcessor(object): 7 | 8 | def process(self, image): 9 | if not self.image_instance: 10 | print("WARNING: Position crop expects image_instance, but none set.") 11 | return image 12 | 13 | return self.process_instance(image, self.image_instance) 14 | 15 | def process_instance(self, image, instance): 16 | raise NotImplementedError 17 | 18 | def get_hash(self): 19 | return self 20 | 21 | 22 | class PositionCrop(BaseInstanceProcessor): 23 | """ 24 | Processor to create custom image crops. Receieves image_instance and implemen 25 | 26 | """ 27 | 28 | def __init__(self, options): 29 | self.options = options 30 | self.crop_position_field = options['crop_field'] 31 | 32 | self.resize_method = options['resize_method'] 33 | self.width = options['width'] 34 | self.height = options['height'] 35 | self.upscale = options['upscale'] or False 36 | 37 | def get_hash(self): 38 | # Hash based on crop value 39 | crop_value = getattr(self.image_instance, self.crop_position_field) 40 | hashed = u"%s-%s-%s-%s" % (self.crop_position_field, crop_value, self.width, self.height) 41 | # print hashed 42 | return hashed 43 | 44 | def get_crop_value(self, instance): 45 | return getattr(instance, self.crop_position_field) 46 | 47 | def process_instance(self, image, instance): 48 | 49 | # Step 1, crop based on crop position 50 | crop_value = self.get_crop_value(instance) 51 | original_width = image.size[0] 52 | original_height = image.size[1] 53 | 54 | # print 'process %s :: original width %s original height %s || SETTINGS %s - %s - %s upscale? %s crop? %s, %s, %s, %s'%(instance, original_width, original_height, self.width, self.height, self.resize_method, self.upscale, crop_value.x, crop_value.y, crop_value.width, crop_value.height) 55 | is_empty_cropper = not crop_value or crop_value == '' or (not crop_value.width and not crop_value.height) 56 | if is_empty_cropper: 57 | # Set crop with inital crop. 58 | 59 | # print 'crop: %s, %s original: %s, %s'%(self.width, self.height, original_width, original_height) 60 | 61 | crop_w = self.width # width if self.width is None else self.width 62 | if self.height and not crop_w: 63 | crop_w = int(float(float(self.height) / float(original_height)) * float(original_width)) 64 | 65 | crop_h = self.height # height if self.height is None else self.height 66 | if self.width and not crop_h: 67 | crop_h = int(float(float(self.width) / float(original_width)) * float(original_height)) 68 | 69 | if self.resize_method == 'fit': 70 | # print "Resize to fit: %s, %s"%(crop_w, crop_h) 71 | resizer = ResizeToFit(width=crop_w, height=crop_h, upscale=self.upscale) 72 | else: 73 | # print "Resize to fill: %s, %s"%(crop_w, crop_h) 74 | resizer = ResizeToFill(width=crop_w, height=crop_h, upscale=self.upscale) 75 | 76 | resized = resizer.process(image) 77 | return resized 78 | 79 | crop_x = 0 if crop_value.x is None else int(0 - crop_value.x) 80 | crop_y = 0 if crop_value.y is None else int(0 - crop_value.y) 81 | crop_w = None if crop_value.width is None else int(crop_value.width) 82 | crop_h = None if crop_value.height is None else int(crop_value.height) 83 | 84 | # print "Resize canvas: %s, %s, %s, %s"%(crop_x, crop_y, crop_w, crop_h) 85 | cropper = ResizeCanvas(crop_w, crop_h, None, None, crop_x, crop_y) 86 | cropped = cropper.process(image) 87 | 88 | # Step 2, resize to correct width and height: 89 | if self.resize_method == 'fit': 90 | width = None if not self.width else self.width 91 | height = None if not self.height else self.height 92 | if not height: 93 | # print 'height = (%s/%s) * %s'%(width, original_width, original_height) 94 | height = int(float(float(width) / float(original_width)) * float(original_height)) 95 | elif not width: 96 | width = int(float(float(height) / float(original_height)) * float(original_width)) 97 | 98 | # print "Resize to fit: %s, %s"%(width, height) 99 | resizer = ResizeToFit(width=width, height=height, upscale=self.upscale) 100 | else: 101 | width = cropped.size[0] if not self.width else self.width 102 | height = cropped.size[1] if not self.height else self.height 103 | # print "Resize to fill: %s, %s"%(crop_w, crop_h) 104 | resizer = ResizeToFill(width=width, height=height, upscale=self.upscale) 105 | 106 | # print 'Resize to %s - %s (%s - %s)'%(width, height, resizer.width, resizer.height) 107 | resized = resizer.process(cropped) 108 | return resized 109 | 110 | 111 | class PositionAndFormatCrop(PositionCrop): 112 | """ 113 | Resize based on crop and format 114 | 115 | """ 116 | 117 | def __init__(self, options): 118 | self.options = options 119 | 120 | self.crop_position_field = options['crop_field'] 121 | self.format_field = options['format_field'] 122 | self.resize_method = options['resize_method'] 123 | self.width = options['width'] 124 | self.height = options['height'] 125 | self.upscale = options['upscale'] or False 126 | 127 | def get_hash(self): 128 | # Hash based on crop value 129 | crop_value = getattr(self.image_instance, self.crop_position_field) 130 | format = getattr(self.image_instance, self.format_field) 131 | hashed = u"%s-%s-%s-%s-%s" % (self.crop_position_field, crop_value, format, self.width, self.height) 132 | # print hashed 133 | return hashed 134 | 135 | 136 | class FormatProcessor(PositionCrop): 137 | """ 138 | Resize based on format; use default crop 139 | 140 | """ 141 | 142 | def __init__(self, options): 143 | self.options = options 144 | self.format_field = options['format_field'] 145 | self.resize_method = options['resize_method'] 146 | self.width = options['width'] 147 | self.height = options['height'] 148 | self.upscale = options['upscale'] or False 149 | 150 | def get_crop_value(self, instance): 151 | return None 152 | 153 | def get_hash(self): 154 | # Hash based on crop value 155 | format = getattr(self.image_instance, self.format_field) 156 | hashed = u"%s-%s-%s" % (format, self.width, self.height) 157 | # print hashed 158 | 159 | return hashed 160 | -------------------------------------------------------------------------------- /imagekit_cropper/fields.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from imagekit.models import ImageSpecField 4 | from imagekit.models.fields.utils import ImageSpecFileDescriptor 5 | from imagekit.registry import generator_registry, unregister 6 | from imagekit.specs import SpecHost, create_spec_class 7 | from imagekit.specs.sourcegroups import ImageFieldSourceGroup 8 | 9 | from .specs import InstanceSpec, InstanceFormatSpec 10 | from .registry import InstanceSourceGroupRegistry, instance_source_group_registry 11 | 12 | 13 | class InstanceSpecField(ImageSpecField): 14 | """ 15 | Image spec field which passes the class instance to the processors 16 | 17 | """ 18 | 19 | 20 | fields = None 21 | source = None 22 | 23 | def __init__(self, processors=None, format=None, options=None, 24 | source=None, cachefile_storage=None, autoconvert=None, 25 | cachefile_backend=None, cachefile_strategy=None, spec=None, 26 | id=None): 27 | 28 | 29 | self.options = options 30 | self.format = format 31 | self.processors = processors 32 | 33 | 34 | #==== FROM ImageSpecField 35 | SpecHost.__init__(self, processors=processors, format=format, 36 | options=options, cachefile_storage=cachefile_storage, 37 | autoconvert=autoconvert, 38 | cachefile_backend=cachefile_backend, 39 | cachefile_strategy=cachefile_strategy, spec=spec, 40 | spec_id=id) 41 | 42 | # TODO: Allow callable for source. See https://github.com/matthewwithanm/django-imagekit/issues/158#issuecomment-10921664 43 | self.source = source 44 | #==== END FROM ImageSpecField 45 | 46 | self.spec = spec = self.get_spec_class() 47 | 48 | 49 | data = self.get_spec_instance_attrs() 50 | for attr in data: 51 | setattr(self.spec, attr, data[attr]) 52 | 53 | spec_id = id 54 | 55 | self._original_spec = spec 56 | 57 | #==== END FROM SpecHost 58 | 59 | def contribute_to_class(self, cls, name): 60 | 61 | descriptor = ImageSpecFileDescriptor(self, name, self.source) 62 | setattr(cls, name, descriptor) 63 | 64 | #Store spec instance info based on spec ID 65 | spec_id = ('%s:%s:%s' % (cls._meta.app_label, 66 | cls._meta.object_name, name)).lower() 67 | 68 | #Un-register with default source group; use custom 69 | unregister.source_group(spec_id, ImageFieldSourceGroup(cls, self.source)) 70 | instance_source_group_registry.register(spec_id, ImageFieldSourceGroup(cls, self.source), self.get_spec_instance_attrs()) 71 | 72 | # setattr(cls, name, InstanceSpecFileDescriptor(self, name, source)) 73 | self._set_spec_id(cls, name) 74 | 75 | def get_spec(self, source): 76 | 77 | item = generator_registry.get(self.spec_id, source=source, specs=self.get_spec_instance_attrs()) 78 | 79 | return item 80 | 81 | 82 | def get_spec_class(self): 83 | return InstanceSpec 84 | 85 | def get_spec_instance_attrs(self): 86 | return {'options': self.options, 'processors': self.processors, 'format': self.format} #override 87 | 88 | 89 | class InstanceFormatSpecField(InstanceSpecField): 90 | 91 | def __init__(self, format_field, processors=None, format=None, options=None, 92 | source=None, cachefile_storage=None, autoconvert=None, 93 | cachefile_backend=None, cachefile_strategy=None, spec=None, 94 | id=None): 95 | 96 | self.format_field = format_field 97 | 98 | super(InstanceFormatSpecField, self).__init__(processors, format, 99 | options, source, cachefile_storage, autoconvert, cachefile_backend, 100 | cachefile_strategy, spec, id) 101 | 102 | def get_spec_instance_attrs(self): 103 | return {'options': self.options, 'processors': self.processors, 'format': self.format, 'format_field':self.format_field} #override 104 | 105 | def get_spec_class(self): 106 | return InstanceFormatSpec 107 | 108 | class CropCoordinates(object): 109 | def __init__(self, x=None, y=None, width=None, height=None): 110 | self.x = x 111 | self.y = y 112 | self.width = width 113 | self.height = height 114 | 115 | def __repr__(self): 116 | if self.width or self.height: 117 | return "%s,%s,%s,%s"%(self.x, self.y, self.width, self.height) 118 | return '' 119 | 120 | class ImageCropField(models.Field): 121 | """ 122 | Model field for containing image crop dimensions 123 | 124 | """ 125 | 126 | description = "Image crop coordinates" 127 | 128 | def __init__(self,properties, help_text=("A comma-separated list of crop coordinates"),verbose_name='imagecropfield', *args,**kwargs): 129 | self.name="ImageCropField", 130 | self.through = None 131 | self.help_text = help_text 132 | self.blank = True 133 | self.editable = True 134 | self.creates_table = False 135 | self.db_column = None 136 | self.serialize = False 137 | self.null = True 138 | self.creation_counter = models.Field.creation_counter 139 | self.properties = properties 140 | # self.default_width = self.properties['width'] if self.properties['width'] else 1000 141 | # self.default_height = self.properties['height'] if self.properties['height'] else 1000 142 | models.Field.creation_counter += 1 143 | super(ImageCropField, self).__init__(*args, **kwargs) 144 | 145 | 146 | def deconstruct(self): 147 | name, path, args, kwargs = super(ImageCropField, self).deconstruct() 148 | kwargs['properties'] = self.properties 149 | return name, path, args, kwargs 150 | 151 | 152 | def db_type(self, connection): 153 | return 'varchar(100)' 154 | 155 | def parse_value(self,value): 156 | if value in ( None,''): 157 | return CropCoordinates() 158 | else: 159 | if isinstance(value, CropCoordinates): 160 | return value 161 | else: 162 | if 'None' in value: 163 | return CropCoordinates() 164 | split_items = value.split(',') 165 | x = float(split_items[0]) 166 | y = float(split_items[1]) 167 | w = float(split_items[2]) 168 | h = float(split_items[3]) 169 | args = [x,y,w,h] 170 | if len(args) != 4 and value is not None: 171 | raise ValidationError("Invalid input for a CropCoordinates instance") 172 | return CropCoordinates(*args) 173 | 174 | def to_python(self,value): 175 | return self.parse_value(value) 176 | 177 | def from_db_value(self, value, expression, connection, context): 178 | return self.parse_value(value) 179 | 180 | def get_prep_value(self, value): 181 | if value: 182 | store_value = ','.join([str(value.x),str(value.y),str(value.width),str(value.height)]) 183 | return store_value 184 | return None 185 | 186 | def get_internal_type(self): 187 | return 'CharField' 188 | 189 | def value_to_string(self, obj): 190 | value = self._get_val_from_obj(obj) 191 | return self.get_prep_value(value) 192 | -------------------------------------------------------------------------------- /imagekit_cropper/utils.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | from django.conf import settings 4 | from django.db import models 5 | from django.db.models.signals import post_init, post_save 6 | from django.dispatch import Signal 7 | from django.utils.functional import wraps 8 | 9 | 10 | 11 | from imagekit import hashers 12 | from imagekit import ImageSpec, register 13 | from imagekit.models import ProcessedImageField 14 | from imagekit.models.fields.utils import ImageSpecFileDescriptor 15 | from imagekit.registry import generator_registry, cachefile_registry 16 | from imagekit.specs.sourcegroups import ImageFieldSourceGroup, ModelSignalRouter, ik_model_receiver 17 | from imagekit.utils import call_strategy_method 18 | from imagekit.cachefiles import ImageCacheFile 19 | from imagekit.exceptions import MissingSource 20 | 21 | 22 | 23 | from pilkit.processors import ProcessorPipeline 24 | from pilkit.utils import open_image, img_to_fobj 25 | 26 | 27 | hack_spec_field_hash = {} 28 | hack_source_field_hash = {} 29 | instance_source_saved = Signal() 30 | 31 | 32 | def instance_ik_model_receiver(fn): 33 | """ 34 | A method decorator that filters out sign_original_specals coming from models that don't 35 | have fields that function as ImageFieldSourceGroup sources. 36 | 37 | """ 38 | @wraps(fn) 39 | def receiver(self, sender, **kwargs): 40 | # print 'inspect.isclass(sender? %s'%(inspect.isclass(sender)) 41 | if not inspect.isclass(sender): 42 | return 43 | for src in self._source_groups: 44 | if issubclass(sender, src.model_class): 45 | fn(self, sender=sender, **kwargs) 46 | 47 | # If we find a match, return. We don't want to handle the signal 48 | # more than once. 49 | return 50 | return receiver 51 | 52 | class InstanceSourceGroupRegistry(object): 53 | """ 54 | The source group registry is responsible for listening to source_* signals 55 | on source groups, and relaying them to the image generated file strategies 56 | of the appropriate generators. 57 | 58 | In addition, registering a new source group also registers its generated 59 | files with that registry. 60 | 61 | """ 62 | _signals = { 63 | instance_source_saved: 'on_source_saved', 64 | } 65 | 66 | def __init__(self): 67 | self._source_groups = {} 68 | for signal in self._signals.keys(): 69 | signal.connect(self.source_group_receiver) 70 | 71 | def register(self, generator_id, source_group): 72 | from imagekit.specs.sourcegroups import SourceGroupFilesGenerator 73 | hack_source_field_hash[source_group] = generator_id 74 | generator_ids = self._source_groups.setdefault(source_group, set()) 75 | generator_ids.add(generator_id) 76 | cachefile_registry.register(generator_id, 77 | SourceGroupFilesGenerator(source_group, generator_id)) 78 | 79 | def unregister(self, generator_id, source_group): 80 | from imagekit.specs.sourcegroups import SourceGroupFilesGenerator 81 | generator_ids = self._source_groups.setdefault(source_group, set()) 82 | if generator_id in generator_ids: 83 | generator_ids.remove(generator_id) 84 | cachefile_registry.unregister(generator_id, 85 | SourceGroupFilesGenerator(source_group, generator_id)) 86 | 87 | def source_group_receiver(self, sender, source, signal, **kwargs): 88 | """ 89 | Relay source group signals to the appropriate spec strategy. 90 | 91 | """ 92 | from imagekit.cachefiles import ImageCacheFile 93 | source_group = sender 94 | 95 | instance = kwargs['instance'] 96 | 97 | # Ignore signals from unregistered groups. 98 | if source_group not in self._source_groups: 99 | return 100 | 101 | #HOOK -- update source to point to image file. 102 | for id in self._source_groups[source_group]: 103 | 104 | spec_to_update = generator_registry.get(id, source=source, instance=instance, field=hack_spec_field_hash[id]) 105 | 106 | specs = [generator_registry.get(id, source=source, instance=instance, field=hack_spec_field_hash[id]) for id in 107 | self._source_groups[source_group]] 108 | callback_name = self._signals[signal] 109 | # print 'callback_name? %s'%(callback_name) 110 | 111 | for spec in specs: 112 | file = ImageCacheFile(spec) 113 | # print 'SEPC %s file %s'%(spec, file) 114 | call_strategy_method(file, callback_name) 115 | 116 | instance_source_group_registry = InstanceSourceGroupRegistry() 117 | 118 | class InstanceModelSignalRouter(object): 119 | 120 | def __init__(self): 121 | self._source_groups = [] 122 | 123 | #NEW HOOK -- tweak uid 124 | uid = 'instance_ik_spec_field_receivers' 125 | post_init.connect(self.post_init_receiver, dispatch_uid=uid) 126 | post_save.connect(self.post_save_receiver, dispatch_uid=uid) 127 | 128 | def add(self, source_group): 129 | self._source_groups.append(source_group) 130 | 131 | def init_instance(self, instance): 132 | instance._ik = getattr(instance, '_ik', {}) 133 | 134 | def update_source_hashes(self, instance): 135 | """ 136 | Stores hashes of the source image files so that they can be compared 137 | later to see whether the source image has changed (and therefore whether 138 | the spec file needs to be regenerated). 139 | 140 | """ 141 | self.init_instance(instance) 142 | instance._ik['source_hashes'] = dict( 143 | (attname, hash(getattr(instance, attname))) 144 | for attname in self.get_source_fields(instance)) 145 | return instance._ik['source_hashes'] 146 | 147 | def get_source_fields(self, instance): 148 | """ 149 | Returns a list of the source fields for the given instance. 150 | """ 151 | return set(src.image_field 152 | for src in self._source_groups 153 | if isinstance(instance, src.model_class)) 154 | 155 | @instance_ik_model_receiver 156 | def post_save_receiver(self, sender, instance=None, created=False, raw=False, **kwargs): 157 | if not raw: 158 | self.init_instance(instance) 159 | old_hashes = instance._ik.get('source_hashes', {}).copy() 160 | new_hashes = self.update_source_hashes(instance) 161 | source_fields = self.get_source_fields(instance) 162 | for attname in source_fields: 163 | file = getattr(instance, attname) 164 | if file: 165 | #TODO -- check here 166 | #and old_hashes.get(attname) != new_hashes[attname]: 167 | self.dispatch_signal(instance_source_saved, file, sender, instance, 168 | attname) 169 | 170 | @instance_ik_model_receiver 171 | def post_init_receiver(self, sender, instance=None, **kwargs): 172 | self.init_instance(instance) 173 | source_fields = self.get_source_fields(instance) 174 | local_fields = dict((field.name, field) 175 | for field in instance._meta.local_fields 176 | if field.name in source_fields) 177 | instance._ik['source_hashes'] = dict( 178 | (attname, hash(file_field)) 179 | for attname, file_field in local_fields.items()) 180 | 181 | 182 | def dispatch_signal(self, signal, file, model_class, instance, attname): 183 | for source_group in self._source_groups: 184 | if issubclass(model_class, source_group.model_class) and source_group.image_field == attname: 185 | #NEW HOOK -- send instance 186 | hack_source_group_lookup = hack_source_field_hash[source_group] 187 | hack_field_lookup = hack_spec_field_hash[hack_source_group_lookup] 188 | signal.send(sender=source_group, source=file, instance=instance, field=hack_field_lookup) 189 | 190 | instance_model_signal_router = InstanceModelSignalRouter() 191 | 192 | class InstanceFieldSourceGroup(ImageFieldSourceGroup): 193 | def __init__(self, model_class, image_field): 194 | self.model_class = model_class 195 | self.image_field = image_field 196 | instance_model_signal_router.add(self) 197 | 198 | class InstanceProcessorPipeline(ProcessorPipeline): 199 | def process(self, img, instance): 200 | for proc in self: 201 | img = proc.process(img, instance) 202 | return img 203 | 204 | 205 | class InstanceSpecFileDescriptor(ImageSpecFileDescriptor): 206 | 207 | 208 | def __get__(self, instance, owner): 209 | # print 'get %s - %s. attname: %s field %s source_field_name: %s'%(instance, owner, self.attname, self.field, self.source_field_name) 210 | 211 | if instance is None: 212 | return self.field 213 | else: 214 | source = getattr(instance, self.source_field_name) 215 | spec = self.field.get_spec(source=source, instance=instance) 216 | 217 | file = ImageCacheFile(spec) 218 | 219 | instance.__dict__[self.attname] = file 220 | return file 221 | 222 | def __set__(self, instance, value): 223 | instance.__dict__[self.attname] = value 224 | 225 | class InstanceSpec(ImageSpec): 226 | 227 | 228 | def __init__(self, source, instance, field): 229 | self.source = source 230 | self.instance = instance 231 | self.field = field 232 | # print 'FIELD %s'%(field.extra_hash_key_values) 233 | # self.extra_hash_key_values = None 234 | 235 | super(InstanceSpec, self).__init__(source) 236 | 237 | def generate(self): 238 | 239 | if not self.source: 240 | raise MissingSource("The spec '%s' has no source file associated" 241 | " with it." % self) 242 | 243 | # TODO: Move into a generator base class 244 | # TODO: Factor out a generate_image function so you can create a generator and only override the PIL.Image creating part. (The tricky part is how to deal with original_format since generator base class won't have one.) 245 | try: 246 | img = open_image(self.source) 247 | except ValueError: 248 | 249 | # Re-open the file -- https://code.djangoproject.com/ticket/13750 250 | self.source.open() 251 | img = open_image(self.source) 252 | 253 | original_format = img.format 254 | 255 | # Run the processors 256 | img = ProcessorPipeline(self.field.processors or []).process(img) 257 | 258 | #HOOK -- now process instance processors 259 | img = InstanceProcessorPipeline(self.field.instance_processors or []).process(img, self.instance) 260 | 261 | format = self.field.format or img.format or original_format or 'JPEG' 262 | options = self.options or {} 263 | fobj = img_to_fobj(img, format, self.autoconvert, **options) 264 | # print 'fobj? %s - %s - %s'%(fobj, img, format) 265 | # print options 266 | return fobj 267 | 268 | def get_hash(self): 269 | 270 | keys = [ 271 | self.source.name, 272 | self.field.processors, 273 | self.field.instance_processors, 274 | self.field.format, 275 | self.field.options, 276 | self.autoconvert, 277 | ] 278 | instance = self.instance 279 | 280 | #Use the actual values of the fields to hash the instance 281 | #REQUIRES INSTANCE: 282 | for extra_field in self.field.extra_hash_key_values: 283 | field = getattr(self.instance, extra_field) 284 | field_hash = "%s_%s"%(extra_field, field) 285 | # print 'field_hash %s'%(field_hash) 286 | keys.append(field_hash) 287 | 288 | # print 'pickle keys: %s'%(keys) 289 | return hashers.pickle(keys) 290 | 291 | 292 | 293 | 294 | -------------------------------------------------------------------------------- /imagekit_cropper/static/imagekit_cropper/jcrop/js/jquery.Jcrop.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jquery.Jcrop.min.js v0.9.12 (build:20130202) 3 | * jQuery Image Cropping Plugin - released under MIT License 4 | * Copyright (c) 2008-2013 Tapmodo Interactive LLC 5 | * https://github.com/tapmodo/Jcrop 6 | */ 7 | (function(a){a.Jcrop=function(b,c){function i(a){return Math.round(a)+"px"}function j(a){return d.baseClass+"-"+a}function k(){return a.fx.step.hasOwnProperty("backgroundColor")}function l(b){var c=a(b).offset();return[c.left,c.top]}function m(a){return[a.pageX-e[0],a.pageY-e[1]]}function n(b){typeof b!="object"&&(b={}),d=a.extend(d,b),a.each(["onChange","onSelect","onRelease","onDblClick"],function(a,b){typeof d[b]!="function"&&(d[b]=function(){})})}function o(a,b,c){e=l(D),bc.setCursor(a==="move"?a:a+"-resize");if(a==="move")return bc.activateHandlers(q(b),v,c);var d=_.getFixed(),f=r(a),g=_.getCorner(r(f));_.setPressed(_.getCorner(f)),_.setCurrent(g),bc.activateHandlers(p(a,d),v,c)}function p(a,b){return function(c){if(!d.aspectRatio)switch(a){case"e":c[1]=b.y2;break;case"w":c[1]=b.y2;break;case"n":c[0]=b.x2;break;case"s":c[0]=b.x2}else switch(a){case"e":c[1]=b.y+1;break;case"w":c[1]=b.y+1;break;case"n":c[0]=b.x+1;break;case"s":c[0]=b.x+1}_.setCurrent(c),bb.update()}}function q(a){var b=a;return bd.watchKeys 8 | (),function(a){_.moveOffset([a[0]-b[0],a[1]-b[1]]),b=a,bb.update()}}function r(a){switch(a){case"n":return"sw";case"s":return"nw";case"e":return"nw";case"w":return"ne";case"ne":return"sw";case"nw":return"se";case"se":return"nw";case"sw":return"ne"}}function s(a){return function(b){return d.disabled?!1:a==="move"&&!d.allowMove?!1:(e=l(D),W=!0,o(a,m(b)),b.stopPropagation(),b.preventDefault(),!1)}}function t(a,b,c){var d=a.width(),e=a.height();d>b&&b>0&&(d=b,e=b/a.width()*a.height()),e>c&&c>0&&(e=c,d=c/a.height()*a.width()),T=a.width()/d,U=a.height()/e,a.width(d).height(e)}function u(a){return{x:a.x*T,y:a.y*U,x2:a.x2*T,y2:a.y2*U,w:a.w*T,h:a.h*U}}function v(a){var b=_.getFixed();b.w>d.minSelect[0]&&b.h>d.minSelect[1]?(bb.enableHandles(),bb.done()):bb.release(),bc.setCursor(d.allowSelect?"crosshair":"default")}function w(a){if(d.disabled)return!1;if(!d.allowSelect)return!1;W=!0,e=l(D),bb.disableHandles(),bc.setCursor("crosshair");var b=m(a);return _.setPressed(b),bb.update(),bc.activateHandlers(x,v,a.type.substring 9 | (0,5)==="touch"),bd.watchKeys(),a.stopPropagation(),a.preventDefault(),!1}function x(a){_.setCurrent(a),bb.update()}function y(){var b=a("
").addClass(j("tracker"));return g&&b.css({opacity:0,backgroundColor:"white"}),b}function be(a){G.removeClass().addClass(j("holder")).addClass(a)}function bf(a,b){function t(){window.setTimeout(u,l)}var c=a[0]/T,e=a[1]/U,f=a[2]/T,g=a[3]/U;if(X)return;var h=_.flipCoords(c,e,f,g),i=_.getFixed(),j=[i.x,i.y,i.x2,i.y2],k=j,l=d.animationDelay,m=h[0]-j[0],n=h[1]-j[1],o=h[2]-j[2],p=h[3]-j[3],q=0,r=d.swingSpeed;c=k[0],e=k[1],f=k[2],g=k[3],bb.animMode(!0);var s,u=function(){return function(){q+=(100-q)/r,k[0]=Math.round(c+q/100*m),k[1]=Math.round(e+q/100*n),k[2]=Math.round(f+q/100*o),k[3]=Math.round(g+q/100*p),q>=99.8&&(q=100),q<100?(bh(k),t()):(bb.done(),bb.animMode(!1),typeof b=="function"&&b.call(bs))}}();t()}function bg(a){bh([a[0]/T,a[1]/U,a[2]/T,a[3]/U]),d.onSelect.call(bs,u(_.getFixed())),bb.enableHandles()}function bh(a){_.setPressed([a[0],a[1]]),_.setCurrent([a[2], 10 | a[3]]),bb.update()}function bi(){return u(_.getFixed())}function bj(){return _.getFixed()}function bk(a){n(a),br()}function bl(){d.disabled=!0,bb.disableHandles(),bb.setCursor("default"),bc.setCursor("default")}function bm(){d.disabled=!1,br()}function bn(){bb.done(),bc.activateHandlers(null,null)}function bo(){G.remove(),A.show(),A.css("visibility","visible"),a(b).removeData("Jcrop")}function bp(a,b){bb.release(),bl();var c=new Image;c.onload=function(){var e=c.width,f=c.height,g=d.boxWidth,h=d.boxHeight;D.width(e).height(f),D.attr("src",a),H.attr("src",a),t(D,g,h),E=D.width(),F=D.height(),H.width(E).height(F),M.width(E+L*2).height(F+L*2),G.width(E).height(F),ba.resize(E,F),bm(),typeof b=="function"&&b.call(bs)},c.src=a}function bq(a,b,c){var e=b||d.bgColor;d.bgFade&&k()&&d.fadeTime&&!c?a.animate({backgroundColor:e},{queue:!1,duration:d.fadeTime}):a.css("backgroundColor",e)}function br(a){d.allowResize?a?bb.enableOnly():bb.enableHandles():bb.disableHandles(),bc.setCursor(d.allowSelect?"crosshair":"default"),bb 11 | .setCursor(d.allowMove?"move":"default"),d.hasOwnProperty("trueSize")&&(T=d.trueSize[0]/E,U=d.trueSize[1]/F),d.hasOwnProperty("setSelect")&&(bg(d.setSelect),bb.done(),delete d.setSelect),ba.refresh(),d.bgColor!=N&&(bq(d.shade?ba.getShades():G,d.shade?d.shadeColor||d.bgColor:d.bgColor),N=d.bgColor),O!=d.bgOpacity&&(O=d.bgOpacity,d.shade?ba.refresh():bb.setBgOpacity(O)),P=d.maxSize[0]||0,Q=d.maxSize[1]||0,R=d.minSize[0]||0,S=d.minSize[1]||0,d.hasOwnProperty("outerImage")&&(D.attr("src",d.outerImage),delete d.outerImage),bb.refresh()}var d=a.extend({},a.Jcrop.defaults),e,f=navigator.userAgent.toLowerCase(),g=/msie/.test(f),h=/msie [1-6]\./.test(f);typeof b!="object"&&(b=a(b)[0]),typeof c!="object"&&(c={}),n(c);var z={border:"none",visibility:"visible",margin:0,padding:0,position:"absolute",top:0,left:0},A=a(b),B=!0;if(b.tagName=="IMG"){if(A[0].width!=0&&A[0].height!=0)A.width(A[0].width),A.height(A[0].height);else{var C=new Image;C.src=A[0].src,A.width(C.width),A.height(C.height)}var D=A.clone().removeAttr("id"). 12 | css(z).show();D.width(A.width()),D.height(A.height()),A.after(D).hide()}else D=A.css(z).show(),B=!1,d.shade===null&&(d.shade=!0);t(D,d.boxWidth,d.boxHeight);var E=D.width(),F=D.height(),G=a("
").width(E).height(F).addClass(j("holder")).css({position:"relative",backgroundColor:d.bgColor}).insertAfter(A).append(D);d.addClass&&G.addClass(d.addClass);var H=a("
"),I=a("
").width("100%").height("100%").css({zIndex:310,position:"absolute",overflow:"hidden"}),J=a("
").width("100%").height("100%").css("zIndex",320),K=a("
").css({position:"absolute",zIndex:600}).dblclick(function(){var a=_.getFixed();d.onDblClick.call(bs,a)}).insertBefore(D).append(I,J);B&&(H=a("").attr("src",D.attr("src")).css(z).width(E).height(F),I.append(H)),h&&K.css({overflowY:"hidden"});var L=d.boundary,M=y().width(E+L*2).height(F+L*2).css({position:"absolute",top:i(-L),left:i(-L),zIndex:290}).mousedown(w),N=d.bgColor,O=d.bgOpacity,P,Q,R,S,T,U,V=!0,W,X,Y;e=l(D);var Z=function(){function a(){var a={},b=["touchstart" 13 | ,"touchmove","touchend"],c=document.createElement("div"),d;try{for(d=0;da+f&&(f-=f+a),0>b+g&&(g-=g+b),FE&&(r=E,u=Math.abs((r-a)/f),s=k<0?b-u:u+b)):(r=c,u=l/f,s=k<0?b-u:b+u,s<0?(s=0,t=Math.abs((s-b)*f),r=j<0?a-t:t+a):s>F&&(s=F,t=Math.abs(s-b)*f,r=j<0?a-t:t+a)),r>a?(r-ah&&(r=a+h),s>b?s=b+(r-a)/f:s=b-(r-a)/f):rh&&(r=a-h),s>b?s=b+(a-r)/f:s=b-(a-r)/f),r<0?(a-=r,r=0):r>E&&(a-=r-E,r=E),s<0?(b-=s,s=0):s>F&&(b-=s-F,s=F),q(o(a,b,r,s))}function n(a){return a[0]<0&&(a[0]=0),a[1]<0&&(a[1]=0),a[0]>E&&(a[0]=E),a[1]>F&&(a[1]=F),[Math.round(a[0]),Math.round(a[1])]}function o(a,b,c,d){var e=a,f=c,g=b,h=d;return cP&&(c=d>0?a+P:a-P),Q&&Math.abs 15 | (f)>Q&&(e=f>0?b+Q:b-Q),S/U&&Math.abs(f)0?b+S/U:b-S/U),R/T&&Math.abs(d)0?a+R/T:a-R/T),a<0&&(c-=a,a-=a),b<0&&(e-=b,b-=b),c<0&&(a-=c,c-=c),e<0&&(b-=e,e-=e),c>E&&(g=c-E,a-=g,c-=g),e>F&&(g=e-F,b-=g,e-=g),a>E&&(g=a-F,e-=g,b-=g),b>F&&(g=b-F,e-=g,b-=g),q(o(a,b,c,e))}function q(a){return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]}}var a=0,b=0,c=0,e=0,f,g;return{flipCoords:o,setPressed:h,setCurrent:i,getOffset:j,moveOffset:k,getCorner:l,getFixed:m}}(),ba=function(){function f(a,b){e.left.css({height:i(b)}),e.right.css({height:i(b)})}function g(){return h(_.getFixed())}function h(a){e.top.css({left:i(a.x),width:i(a.w),height:i(a.y)}),e.bottom.css({top:i(a.y2),left:i(a.x),width:i(a.w),height:i(F-a.y2)}),e.right.css({left:i(a.x2),width:i(E-a.x2)}),e.left.css({width:i(a.x)})}function j(){return a("
").css({position:"absolute",backgroundColor:d.shadeColor||d.bgColor}).appendTo(c)}function k(){b||(b=!0,c.insertBefore(D),g(),bb.setBgOpacity(1,0,1),H.hide(),l(d.shadeColor||d.bgColor,1),bb. 16 | isAwake()?n(d.bgOpacity,1):n(1,1))}function l(a,b){bq(p(),a,b)}function m(){b&&(c.remove(),H.show(),b=!1,bb.isAwake()?bb.setBgOpacity(d.bgOpacity,1,1):(bb.setBgOpacity(1,1,1),bb.disableHandles()),bq(G,0,1))}function n(a,e){b&&(d.bgFade&&!e?c.animate({opacity:1-a},{queue:!1,duration:d.fadeTime}):c.css({opacity:1-a}))}function o(){d.shade?k():m(),bb.isAwake()&&n(d.bgOpacity)}function p(){return c.children()}var b=!1,c=a("
").css({position:"absolute",zIndex:240,opacity:0}),e={top:j(),left:j().height(F),right:j().height(F),bottom:j()};return{update:g,updateRaw:h,getShades:p,setBgColor:l,enable:k,disable:m,resize:f,refresh:o,opacity:n}}(),bb=function(){function k(b){var c=a("
").css({position:"absolute",opacity:d.borderOpacity}).addClass(j(b));return I.append(c),c}function l(b,c){var d=a("
").mousedown(s(b)).css({cursor:b+"-resize",position:"absolute",zIndex:c}).addClass("ord-"+b);return Z.support&&d.bind("touchstart.jcrop",Z.createDragger(b)),J.append(d),d}function m(a){var b=d.handleSize,e=l(a,c++ 17 | ).css({opacity:d.handleOpacity}).addClass(j("handle"));return b&&e.width(b).height(b),e}function n(a){return l(a,c++).addClass("jcrop-dragbar")}function o(a){var b;for(b=0;b').css({position:"fixed",left:"-120px",width:"12px"}).addClass("jcrop-keymgr"),c=a("
").css({position:"absolute",overflow:"hidden"}).append(b);return d.keySupport&&(b.keydown(i).blur(f),h||!d.fixedSupport?(b.css({position:"absolute",left:"-20px"}),c.append(b).insertBefore(D)):b.insertBefore(D)),{watchKeys:e}}();Z.support&&M.bind("touchstart.jcrop",Z.newSelection),J.hide(),br(!0);var bs={setImage:bp,animateTo:bf,setSelect:bg,setOptions:bk,tellSelect:bi,tellScaled:bj,setClass:be,disable:bl,enable:bm,cancel:bn,release:bb.release,destroy:bo,focus:bd.watchKeys,getBounds:function(){return[E*T,F*U]},getWidgetSize:function(){return[E,F]},getScaleFactor:function(){return[T,U]},getOptions:function(){return d},ui:{holder:G,selection:K}};return g&&G.bind("selectstart",function(){return!1}),A.data("Jcrop",bs),bs},a.fn.Jcrop=function(b,c){var d;return this.each(function(){if(a(this).data("Jcrop")){if( 21 | b==="api")return a(this).data("Jcrop");a(this).data("Jcrop").setOptions(b)}else this.tagName=="IMG"?a.Jcrop.Loader(this,function(){a(this).css({display:"block",visibility:"hidden"}),d=a.Jcrop(this,b),a.isFunction(c)&&c.call(d)}):(a(this).css({display:"block",visibility:"hidden"}),d=a.Jcrop(this,b),a.isFunction(c)&&c.call(d))}),this},a.Jcrop.Loader=function(b,c,d){function g(){f.complete?(e.unbind(".jcloader"),a.isFunction(c)&&c.call(f)):window.setTimeout(g,50)}var e=a(b),f=e[0];e.bind("load.jcloader",g).bind("error.jcloader",function(b){e.unbind(".jcloader"),a.isFunction(d)&&d.call(f)}),f.complete&&a.isFunction(c)&&(e.unbind(".jcloader"),c.call(f))},a.Jcrop.defaults={allowSelect:!0,allowMove:!0,allowResize:!0,trackDocument:!0,baseClass:"jcrop",addClass:null,bgColor:"black",bgOpacity:.6,bgFade:!1,borderOpacity:.4,handleOpacity:.5,handleSize:null,aspectRatio:0,keySupport:!0,createHandles:["n","s","e","w","nw","ne","se","sw"],createDragbars:["n","s","e","w"],createBorders:["n","s","e","w"],drawBorders:!0,dragEdges 22 | :!0,fixedSupport:!0,touchSupport:null,shade:null,boxWidth:0,boxHeight:0,boundary:2,fadeTime:400,animationDelay:20,swingSpeed:3,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){},onDblClick:function(){},onRelease:function(){}}})(jQuery); -------------------------------------------------------------------------------- /imagekit_cropper/static/imagekit_cropper/js/imagecrop.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * nina@ninalp.com 3 | */ 4 | 5 | $image_cropper_jquery = jQuery.noConflict(); 6 | 7 | ;(function ( $, window, document, undefined ) { 8 | 9 | 10 | // Create the defaults once 11 | var pluginName = "imageCropWidget", 12 | defaults = { 13 | imagePreviewWidth: "280px", 14 | maxUpscale: 2, 15 | warningUpscale: 1.5 16 | }; 17 | 18 | // The actual plugin constructor 19 | function ImageCropWidget( element, options ) { 20 | this.element = element; 21 | 22 | this.options = $.extend( {}, defaults, options) ; 23 | 24 | this._defaults = defaults; 25 | this._name = pluginName; 26 | 27 | if (typeof window['image_crops'] == "undefined") { 28 | window['image_crops'] = {}; 29 | } 30 | 31 | 32 | this.init(); 33 | } 34 | 35 | ImageCropWidget.prototype = { 36 | 37 | init: function() { 38 | // this.jcrop_api; 39 | this.input_container = $(this.element).find('input')[0]; 40 | 41 | this.original_width = -1; 42 | this.original_height = -1; 43 | 44 | this.crop_container = $(this.element).find('.image-cropper')[0]; 45 | this.data_container = $(this.element).find('.image-crop-data')[0]; 46 | this.target_width = parseInt($(this.crop_container).data('width')); 47 | this.target_height = parseInt($(this.crop_container).data('height')); 48 | this.target_ratio = parseFloat($(this.crop_container).data('ratio')); 49 | this.target_resize_method = $(this.crop_container).data('resize-method'); 50 | this.target_source = $(this.crop_container).data('source'); 51 | this.target_upscale = $(this.crop_container).data('upscale').toLowerCase()=='true'; 52 | //console.log("upscale? "+$(this.crop_container).data('upscale')) 53 | 54 | $(this.input_container).attr('data-original-value',$(this.input_container).attr('value')); 55 | 56 | this.help_text_container = $(this.data_container).find('.grp-help')[0]; 57 | 58 | $(this.data_container).append( '

' ); 59 | this.upscale_container = $(this.data_container).find('.upscale')[0]; 60 | 61 | this.setImageSource(this.getSourceImage(this.target_source)); 62 | 63 | 64 | this.addListeners(); 65 | 66 | this.render() 67 | }, 68 | initialRender: function(){ 69 | 70 | //destroy any previous instances 71 | //TODO 72 | // if(this.jcrop_api){ 73 | // console.log('destroy previous jcrop api.: '+this.jcrop_api+" "+typeof(this.jcrop_api)) 74 | // this.jcrop_api.destroy(); 75 | // this.jcrop_api = null; 76 | // } 77 | 78 | //create fresh instances... 79 | $(this.crop_container).html($('')); 80 | var parent = this; 81 | this.image = $(this.crop_container).find('img')[0]; 82 | 83 | 84 | if(this.image_source in window['image_crops']){ 85 | 86 | var loaded = window['image_crops'][this.image_source]['loaded']; 87 | var image = window['image_crops'][this.image_source]['image']; 88 | if(loaded==true){ 89 | this.original_width = window['image_crops'][this.image_source]['original_width']; 90 | this.original_height = window['image_crops'][this.image_source]['original_height']; 91 | this.image_preview_source = window['image_crops'][this.image_source]['preview']; 92 | this.render(); 93 | }else{ 94 | image.load(function(event) { 95 | parent.imageLoaded(event, this); 96 | }); 97 | } 98 | 99 | }else{ 100 | 101 | 102 | this.original_width = -1; 103 | this.original_height = -1; 104 | var image = $(""); 105 | if(this.image_source.substring(0,4).toLowerCase()==='http') { 106 | $(image).attr("crossorigin", "anonymous"); 107 | } 108 | window['image_crops'][this.image_source] = {'loaded':false,'image':image} 109 | 110 | image.load(function(event) { 111 | 112 | window['image_crops'][parent.image_source]['loaded'] = true; 113 | window['image_crops'][parent.image_source]['original_width'] = this.width; 114 | window['image_crops'][parent.image_source]['original_height'] = this.height; 115 | window['image_crops'][parent.image_source]['preview'] = parent.getResizedImage(this, $(parent.crop_container).width()) 116 | 117 | parent.imageLoaded(event, this); 118 | }); 119 | 120 | var cachebuster = new Date().getTime(); 121 | var cache_string = this.image_source.toLowerCase().indexOf('data:') >= 0? '' : "?v="+cachebuster; 122 | image.attr("src", this.image_source+cache_string); 123 | 124 | } 125 | 126 | 127 | 128 | }, 129 | imageLoaded: function(event, image){ 130 | 131 | this.original_width = image.width; 132 | this.original_height = image.height; 133 | this.image_preview_source = window['image_crops'][this.image_source]['preview']; 134 | 135 | this.render(); 136 | }, 137 | isCanvasSupported:function (){ 138 | var elem = document.createElement('canvas'); 139 | return !!(elem.getContext && elem.getContext('2d')); 140 | }, 141 | getResizedImage:function(image, MAX_WIDTH, MAX_HEIGHT){ 142 | 143 | if(this.isCanvasSupported()==false){ 144 | //no canvas support :( 145 | return self.image_source; 146 | } 147 | 148 | var canvas = document.createElement('canvas'); 149 | 150 | if(typeof(MAX_WIDTH) == 'undefined'){ 151 | MAX_WIDTH = 800 152 | } 153 | if(typeof(MAX_HEIGHT) == 'undefined'){ 154 | MAX_HEIGHT = 600 155 | } 156 | 157 | var width = image.width; 158 | var height = image.height; 159 | 160 | if (width > height) { 161 | if (width > MAX_WIDTH) { 162 | height *= MAX_WIDTH / width; 163 | width = MAX_WIDTH; 164 | } 165 | } else { 166 | if (height > MAX_HEIGHT) { 167 | width *= MAX_HEIGHT / height; 168 | height = MAX_HEIGHT; 169 | } 170 | } 171 | canvas.width = width; 172 | canvas.height = height; 173 | 174 | var ctx = canvas.getContext("2d"); 175 | ctx.drawImage(image, 0, 0, width, height); 176 | 177 | var dataurl = canvas.toDataURL("image/png"); 178 | return dataurl; 179 | }, 180 | setImageSource: function(source) { 181 | this.image_source = source; 182 | this.initialRender(); 183 | //this.render(); 184 | }, 185 | setCropValue:function(preview_crop){ 186 | crop = this.cropToRealValues(preview_crop); 187 | var pickled = crop.x+","+crop.y+","+crop.w+","+crop.h; 188 | $(this.input_container).val(pickled); 189 | $(this.input_container).attr('value',pickled); 190 | 191 | 192 | this.updateScaleNotification(); 193 | 194 | }, 195 | getCropValue:function(){ 196 | var pickled = $(this.input_container).attr('value'); 197 | if(pickled=='' || typeof(pickled) == 'undefined'){ 198 | //find a center crop. 199 | var real = this.getResetCrop() 200 | 201 | }else{ 202 | var split = pickled.split(','); 203 | var real = {'x':parseInt(split[0]),'y':parseInt(split[1]),'w':parseInt(split[2]),'h':parseInt(split[3])} 204 | } 205 | 206 | return this.cropToPreviewValues(real) 207 | }, 208 | cropToPreviewValues:function(crop){ 209 | return {'x':this.scaleToPreview(crop.x),'y':this.scaleToPreview(crop.y),'w':this.scaleToPreview(crop.w),'h':this.scaleToPreview(crop.h)} 210 | }, 211 | cropToRealValues:function(crop){ 212 | return {'x':this.scaleToReal(crop.x),'y':this.scaleToReal(crop.y),'w':this.scaleToReal(crop.w),'h':this.scaleToReal(crop.h)} 213 | }, 214 | scaleToPreview:function(value){ 215 | var scaler = $(this.image).width() / this.original_width; 216 | return Math.round(value*scaler); 217 | }, 218 | scaleToReal:function(value){ 219 | var scaler = this.original_width / $(this.image).width(); 220 | return Math.round(value*scaler); 221 | }, 222 | getResetCrop:function(){ 223 | 224 | if(isNaN(this.target_height) || isNaN(this.target_width)){ 225 | var x = 0; 226 | var y = 0; 227 | var w = this.original_width; 228 | var h = this.original_height; 229 | 230 | //console.log("upscale? "+this.target_upscale+" target_w: "+this.target_width+" w: "+w+" target_height: "+this.target_height+" h? "+h) 231 | }else{ 232 | var target_aspect_ratio = this.target_width / this.target_height; 233 | var current_aspect_ratio = this.original_width / this.original_height; 234 | 235 | if(target_aspect_ratio < current_aspect_ratio){ 236 | //then image height is our limiting factor 237 | //and the crop width will be < the original width 238 | var h = this.original_height; 239 | var w = target_aspect_ratio * this.original_height; 240 | var y = 0; 241 | var x = 0.5*(this.original_width - w); 242 | 243 | }else{ 244 | //then image width is our limiting factor 245 | //and the crop height will < the original height 246 | var w = this.original_width; 247 | var h = this.original_width / target_aspect_ratio; 248 | var x = 0; 249 | var y = 0.5*(this.original_height - h); 250 | 251 | } 252 | 253 | } 254 | 255 | 256 | 257 | reset = {'x':x,'y':y,'w':w,'h':h} 258 | // console.log("getResetCrop: "+reset) 259 | return reset; 260 | }, 261 | updateCoordinates: function(c){ 262 | this.setCropValue(c); 263 | }, 264 | render: function() { 265 | 266 | if(this.original_width < 0 || this.original_height < 0){ 267 | //not ready. wait for original image to load 268 | return; 269 | } 270 | 271 | //Update view 272 | var parent = this; 273 | $(this.image).attr('width', this.options.imagePreviewWidth); 274 | $(this.image).attr('src', this.image_preview_source); 275 | 276 | var aspect_ratio = isNaN(this.target_ratio)? this.target_width / this.target_height : this.target_ratio; 277 | 278 | var initial_crop = this.getCropValue(); 279 | var minW = 1; 280 | var minH = 1; 281 | if(this.target_upscale==false){ 282 | 283 | if(this.target_width > this.original_width){ 284 | minW = this.scaleToPreview(this.original_width); 285 | }else{ 286 | minW = this.scaleToPreview(this.target_width); 287 | } 288 | 289 | if(this.target_height > this.original_height){ 290 | minH = this.scaleToPreview(this.original_height); 291 | }else{ 292 | minH = this.scaleToPreview(this.target_height); 293 | } 294 | } 295 | 296 | 297 | 298 | $(this.image).Jcrop({ 299 | onSelect: function(c){ 300 | parent.updateCoordinates(c) 301 | }, 302 | onChange: function(c){ 303 | parent.updateCoordinates(c) 304 | }, 305 | setSelect: [ initial_crop.x, initial_crop.y, initial_crop.x+initial_crop.w, initial_crop.y+initial_crop.h ], 306 | aspectRatio: aspect_ratio, 307 | minSize: [minW, minH], 308 | allowSelect: false 309 | }); 310 | }, 311 | updateScaleNotification: function(){ 312 | 313 | var scale_w = this.target_width / crop.w; 314 | var scale_h = this.target_height / crop.h; 315 | 316 | var warning_upscale = scale_w > 1.01 || scale_h > 1.01 317 | var warning_upscale_high = scale_w > this.options.warningUpscale || scale_h > this.options.warningUpscale; 318 | 319 | if(warning_upscale){ 320 | var max = isNaN(scale_w)? scale_h : isNaN(scale_h)? scale_w : Math.max(scale_w, scale_h); 321 | $(this.upscale_container).text('This crop will up-scale the image by '+(this.formatUpscale(max))+"%. Increase your bounding box or upload a larger source image if possible."); 322 | } 323 | 324 | if(this.target_upscale && warning_upscale){ 325 | $(this.upscale_container).show(); 326 | }else{ 327 | $(this.upscale_container).hide(); 328 | } 329 | 330 | if(this.target_upscale && warning_upscale_high){ 331 | $(this.upscale_container).addClass('warning'); 332 | }else{ 333 | $(this.upscale_container).removeClass('warning'); 334 | } 335 | 336 | 337 | }, 338 | formatUpscale:function(number){ 339 | return parseInt(100*(number-1)); 340 | }, 341 | addListeners: function() { 342 | //bind events 343 | var parent = this; 344 | $('#id_'+this.target_source).bind('change', function(event){ 345 | var input = this; 346 | if (input.files && input.files[0]) { 347 | var reader = new FileReader(); 348 | reader.onload = function (e) { 349 | $(parent.input_container).attr('value', ''); 350 | $(parent.input_container).val(''); 351 | parent.setImageSource(e.target.result); 352 | 353 | } 354 | reader.readAsDataURL(input.files[0]); 355 | } 356 | }); 357 | 358 | $(this.input_container).bind('change', function(event){ 359 | $(parent.input_container).attr('value', $(parent.input_container).val()) 360 | parent.render(); 361 | }) 362 | }, 363 | 364 | removeListeners: function() { 365 | //unbind events 366 | $('#id_'+this.target_source).unbind('change'); 367 | $(this.input_container).unbind('change'); 368 | }, 369 | getSourceImage:function(image_name){ 370 | var current_value = $('.grp-cell.'+image_name+", .grp-row."+image_name).find(".file-upload a").attr('href') 371 | var input_value = $('#id_'+image_name).attr('value'); 372 | 373 | if(typeof(input_value) == 'undefined'){ 374 | if(typeof(current_value) == 'undefined'){ 375 | return ''; 376 | } 377 | return current_value; 378 | } 379 | return input_value; 380 | 381 | } 382 | 383 | }; 384 | 385 | // A really lightweight plugin wrapper around the constructor, 386 | // preventing against multiple instantiations 387 | $.fn[pluginName] = function ( options ) { 388 | return this.each(function () { 389 | if (!$.data(this, "plugin_" + pluginName)) { 390 | $.data(this, "plugin_" + pluginName, 391 | new ImageCropWidget( this, options )); 392 | } 393 | }); 394 | }; 395 | 396 | })( $image_cropper_jquery, window, document ); 397 | 398 | 399 | $image_cropper_jquery( document ).ready(function() { 400 | $image_cropper_jquery(".image-crop-container").imageCropWidget(); 401 | }); 402 | 403 | 404 | -------------------------------------------------------------------------------- /imagekit_cropper/static/imagekit_cropper/jcrop/js/jquery.color.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Color Animations v2.0pre 3 | * http://jquery.org/ 4 | * 5 | * Copyright 2011 John Resig 6 | * Dual licensed under the MIT or GPL Version 2 licenses. 7 | * http://jquery.org/license 8 | */ 9 | 10 | (function( jQuery, undefined ){ 11 | var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color outlineColor".split(" "), 12 | 13 | // plusequals test for += 100 -= 100 14 | rplusequals = /^([\-+])=\s*(\d+\.?\d*)/, 15 | // a set of RE's that can match strings and generate color tuples. 16 | stringParsers = [{ 17 | re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, 18 | parse: function( execResult ) { 19 | return [ 20 | execResult[ 1 ], 21 | execResult[ 2 ], 22 | execResult[ 3 ], 23 | execResult[ 4 ] 24 | ]; 25 | } 26 | }, { 27 | re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, 28 | parse: function( execResult ) { 29 | return [ 30 | 2.55 * execResult[1], 31 | 2.55 * execResult[2], 32 | 2.55 * execResult[3], 33 | execResult[ 4 ] 34 | ]; 35 | } 36 | }, { 37 | re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, 38 | parse: function( execResult ) { 39 | return [ 40 | parseInt( execResult[ 1 ], 16 ), 41 | parseInt( execResult[ 2 ], 16 ), 42 | parseInt( execResult[ 3 ], 16 ) 43 | ]; 44 | } 45 | }, { 46 | re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/, 47 | parse: function( execResult ) { 48 | return [ 49 | parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ), 50 | parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ), 51 | parseInt( execResult[ 3 ] + execResult[ 3 ], 16 ) 52 | ]; 53 | } 54 | }, { 55 | re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, 56 | space: "hsla", 57 | parse: function( execResult ) { 58 | return [ 59 | execResult[1], 60 | execResult[2] / 100, 61 | execResult[3] / 100, 62 | execResult[4] 63 | ]; 64 | } 65 | }], 66 | 67 | // jQuery.Color( ) 68 | color = jQuery.Color = function( color, green, blue, alpha ) { 69 | return new jQuery.Color.fn.parse( color, green, blue, alpha ); 70 | }, 71 | spaces = { 72 | rgba: { 73 | cache: "_rgba", 74 | props: { 75 | red: { 76 | idx: 0, 77 | type: "byte", 78 | empty: true 79 | }, 80 | green: { 81 | idx: 1, 82 | type: "byte", 83 | empty: true 84 | }, 85 | blue: { 86 | idx: 2, 87 | type: "byte", 88 | empty: true 89 | }, 90 | alpha: { 91 | idx: 3, 92 | type: "percent", 93 | def: 1 94 | } 95 | } 96 | }, 97 | hsla: { 98 | cache: "_hsla", 99 | props: { 100 | hue: { 101 | idx: 0, 102 | type: "degrees", 103 | empty: true 104 | }, 105 | saturation: { 106 | idx: 1, 107 | type: "percent", 108 | empty: true 109 | }, 110 | lightness: { 111 | idx: 2, 112 | type: "percent", 113 | empty: true 114 | } 115 | } 116 | } 117 | }, 118 | propTypes = { 119 | "byte": { 120 | floor: true, 121 | min: 0, 122 | max: 255 123 | }, 124 | "percent": { 125 | min: 0, 126 | max: 1 127 | }, 128 | "degrees": { 129 | mod: 360, 130 | floor: true 131 | } 132 | }, 133 | rgbaspace = spaces.rgba.props, 134 | support = color.support = {}, 135 | 136 | // colors = jQuery.Color.names 137 | colors, 138 | 139 | // local aliases of functions called often 140 | each = jQuery.each; 141 | 142 | spaces.hsla.props.alpha = rgbaspace.alpha; 143 | 144 | function clamp( value, prop, alwaysAllowEmpty ) { 145 | var type = propTypes[ prop.type ] || {}, 146 | allowEmpty = prop.empty || alwaysAllowEmpty; 147 | 148 | if ( allowEmpty && value == null ) { 149 | return null; 150 | } 151 | if ( prop.def && value == null ) { 152 | return prop.def; 153 | } 154 | if ( type.floor ) { 155 | value = ~~value; 156 | } else { 157 | value = parseFloat( value ); 158 | } 159 | if ( value == null || isNaN( value ) ) { 160 | return prop.def; 161 | } 162 | if ( type.mod ) { 163 | value = value % type.mod; 164 | // -10 -> 350 165 | return value < 0 ? type.mod + value : value; 166 | } 167 | 168 | // for now all property types without mod have min and max 169 | return type.min > value ? type.min : type.max < value ? type.max : value; 170 | } 171 | 172 | function stringParse( string ) { 173 | var inst = color(), 174 | rgba = inst._rgba = []; 175 | 176 | string = string.toLowerCase(); 177 | 178 | each( stringParsers, function( i, parser ) { 179 | var match = parser.re.exec( string ), 180 | values = match && parser.parse( match ), 181 | parsed, 182 | spaceName = parser.space || "rgba", 183 | cache = spaces[ spaceName ].cache; 184 | 185 | 186 | if ( values ) { 187 | parsed = inst[ spaceName ]( values ); 188 | 189 | // if this was an rgba parse the assignment might happen twice 190 | // oh well.... 191 | inst[ cache ] = parsed[ cache ]; 192 | rgba = inst._rgba = parsed._rgba; 193 | 194 | // exit each( stringParsers ) here because we matched 195 | return false; 196 | } 197 | }); 198 | 199 | // Found a stringParser that handled it 200 | if ( rgba.length !== 0 ) { 201 | 202 | // if this came from a parsed string, force "transparent" when alpha is 0 203 | // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0) 204 | if ( Math.max.apply( Math, rgba ) === 0 ) { 205 | jQuery.extend( rgba, colors.transparent ); 206 | } 207 | return inst; 208 | } 209 | 210 | // named colors / default - filter back through parse function 211 | if ( string = colors[ string ] ) { 212 | return string; 213 | } 214 | } 215 | 216 | color.fn = color.prototype = { 217 | constructor: color, 218 | parse: function( red, green, blue, alpha ) { 219 | if ( red === undefined ) { 220 | this._rgba = [ null, null, null, null ]; 221 | return this; 222 | } 223 | if ( red instanceof jQuery || red.nodeType ) { 224 | red = red instanceof jQuery ? red.css( green ) : jQuery( red ).css( green ); 225 | green = undefined; 226 | } 227 | 228 | var inst = this, 229 | type = jQuery.type( red ), 230 | rgba = this._rgba = [], 231 | source; 232 | 233 | // more than 1 argument specified - assume ( red, green, blue, alpha ) 234 | if ( green !== undefined ) { 235 | red = [ red, green, blue, alpha ]; 236 | type = "array"; 237 | } 238 | 239 | if ( type === "string" ) { 240 | return this.parse( stringParse( red ) || colors._default ); 241 | } 242 | 243 | if ( type === "array" ) { 244 | each( rgbaspace, function( key, prop ) { 245 | rgba[ prop.idx ] = clamp( red[ prop.idx ], prop ); 246 | }); 247 | return this; 248 | } 249 | 250 | if ( type === "object" ) { 251 | if ( red instanceof color ) { 252 | each( spaces, function( spaceName, space ) { 253 | if ( red[ space.cache ] ) { 254 | inst[ space.cache ] = red[ space.cache ].slice(); 255 | } 256 | }); 257 | } else { 258 | each( spaces, function( spaceName, space ) { 259 | each( space.props, function( key, prop ) { 260 | var cache = space.cache; 261 | 262 | // if the cache doesn't exist, and we know how to convert 263 | if ( !inst[ cache ] && space.to ) { 264 | 265 | // if the value was null, we don't need to copy it 266 | // if the key was alpha, we don't need to copy it either 267 | if ( red[ key ] == null || key === "alpha") { 268 | return; 269 | } 270 | inst[ cache ] = space.to( inst._rgba ); 271 | } 272 | 273 | // this is the only case where we allow nulls for ALL properties. 274 | // call clamp with alwaysAllowEmpty 275 | inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true ); 276 | }); 277 | }); 278 | } 279 | return this; 280 | } 281 | }, 282 | is: function( compare ) { 283 | var is = color( compare ), 284 | same = true, 285 | myself = this; 286 | 287 | each( spaces, function( _, space ) { 288 | var isCache = is[ space.cache ], 289 | localCache; 290 | if (isCache) { 291 | localCache = myself[ space.cache ] || space.to && space.to( myself._rgba ) || []; 292 | each( space.props, function( _, prop ) { 293 | if ( isCache[ prop.idx ] != null ) { 294 | same = ( isCache[ prop.idx ] === localCache[ prop.idx ] ); 295 | return same; 296 | } 297 | }); 298 | } 299 | return same; 300 | }); 301 | return same; 302 | }, 303 | _space: function() { 304 | var used = [], 305 | inst = this; 306 | each( spaces, function( spaceName, space ) { 307 | if ( inst[ space.cache ] ) { 308 | used.push( spaceName ); 309 | } 310 | }); 311 | return used.pop(); 312 | }, 313 | transition: function( other, distance ) { 314 | var end = color( other ), 315 | spaceName = end._space(), 316 | space = spaces[ spaceName ], 317 | start = this[ space.cache ] || space.to( this._rgba ), 318 | result = start.slice(); 319 | 320 | end = end[ space.cache ]; 321 | each( space.props, function( key, prop ) { 322 | var index = prop.idx, 323 | startValue = start[ index ], 324 | endValue = end[ index ], 325 | type = propTypes[ prop.type ] || {}; 326 | 327 | // if null, don't override start value 328 | if ( endValue === null ) { 329 | return; 330 | } 331 | // if null - use end 332 | if ( startValue === null ) { 333 | result[ index ] = endValue; 334 | } else { 335 | if ( type.mod ) { 336 | if ( endValue - startValue > type.mod / 2 ) { 337 | startValue += type.mod; 338 | } else if ( startValue - endValue > type.mod / 2 ) { 339 | startValue -= type.mod; 340 | } 341 | } 342 | result[ prop.idx ] = clamp( ( endValue - startValue ) * distance + startValue, prop ); 343 | } 344 | }); 345 | return this[ spaceName ]( result ); 346 | }, 347 | blend: function( opaque ) { 348 | // if we are already opaque - return ourself 349 | if ( this._rgba[ 3 ] === 1 ) { 350 | return this; 351 | } 352 | 353 | var rgb = this._rgba.slice(), 354 | a = rgb.pop(), 355 | blend = color( opaque )._rgba; 356 | 357 | return color( jQuery.map( rgb, function( v, i ) { 358 | return ( 1 - a ) * blend[ i ] + a * v; 359 | })); 360 | }, 361 | toRgbaString: function() { 362 | var prefix = "rgba(", 363 | rgba = jQuery.map( this._rgba, function( v, i ) { 364 | return v == null ? ( i > 2 ? 1 : 0 ) : v; 365 | }); 366 | 367 | if ( rgba[ 3 ] === 1 ) { 368 | rgba.pop(); 369 | prefix = "rgb("; 370 | } 371 | 372 | return prefix + rgba.join(",") + ")"; 373 | }, 374 | toHslaString: function() { 375 | var prefix = "hsla(", 376 | hsla = jQuery.map( this.hsla(), function( v, i ) { 377 | if ( v == null ) { 378 | v = i > 2 ? 1 : 0; 379 | } 380 | 381 | // catch 1 and 2 382 | if ( i && i < 3 ) { 383 | v = Math.round( v * 100 ) + "%"; 384 | } 385 | return v; 386 | }); 387 | 388 | if ( hsla[ 3 ] === 1 ) { 389 | hsla.pop(); 390 | prefix = "hsl("; 391 | } 392 | return prefix + hsla.join(",") + ")"; 393 | }, 394 | toHexString: function( includeAlpha ) { 395 | var rgba = this._rgba.slice(), 396 | alpha = rgba.pop(); 397 | 398 | if ( includeAlpha ) { 399 | rgba.push( ~~( alpha * 255 ) ); 400 | } 401 | 402 | return "#" + jQuery.map( rgba, function( v, i ) { 403 | 404 | // default to 0 when nulls exist 405 | v = ( v || 0 ).toString( 16 ); 406 | return v.length === 1 ? "0" + v : v; 407 | }).join(""); 408 | }, 409 | toString: function() { 410 | return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString(); 411 | } 412 | }; 413 | color.fn.parse.prototype = color.fn; 414 | 415 | // hsla conversions adapted from: 416 | // http://www.google.com/codesearch/p#OAMlx_jo-ck/src/third_party/WebKit/Source/WebCore/inspector/front-end/Color.js&d=7&l=193 417 | 418 | function hue2rgb( p, q, h ) { 419 | h = ( h + 1 ) % 1; 420 | if ( h * 6 < 1 ) { 421 | return p + (q - p) * 6 * h; 422 | } 423 | if ( h * 2 < 1) { 424 | return q; 425 | } 426 | if ( h * 3 < 2 ) { 427 | return p + (q - p) * ((2/3) - h) * 6; 428 | } 429 | return p; 430 | } 431 | 432 | spaces.hsla.to = function ( rgba ) { 433 | if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) { 434 | return [ null, null, null, rgba[ 3 ] ]; 435 | } 436 | var r = rgba[ 0 ] / 255, 437 | g = rgba[ 1 ] / 255, 438 | b = rgba[ 2 ] / 255, 439 | a = rgba[ 3 ], 440 | max = Math.max( r, g, b ), 441 | min = Math.min( r, g, b ), 442 | diff = max - min, 443 | add = max + min, 444 | l = add * 0.5, 445 | h, s; 446 | 447 | if ( min === max ) { 448 | h = 0; 449 | } else if ( r === max ) { 450 | h = ( 60 * ( g - b ) / diff ) + 360; 451 | } else if ( g === max ) { 452 | h = ( 60 * ( b - r ) / diff ) + 120; 453 | } else { 454 | h = ( 60 * ( r - g ) / diff ) + 240; 455 | } 456 | 457 | if ( l === 0 || l === 1 ) { 458 | s = l; 459 | } else if ( l <= 0.5 ) { 460 | s = diff / add; 461 | } else { 462 | s = diff / ( 2 - add ); 463 | } 464 | return [ Math.round(h) % 360, s, l, a == null ? 1 : a ]; 465 | }; 466 | 467 | spaces.hsla.from = function ( hsla ) { 468 | if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) { 469 | return [ null, null, null, hsla[ 3 ] ]; 470 | } 471 | var h = hsla[ 0 ] / 360, 472 | s = hsla[ 1 ], 473 | l = hsla[ 2 ], 474 | a = hsla[ 3 ], 475 | q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s, 476 | p = 2 * l - q, 477 | r, g, b; 478 | 479 | return [ 480 | Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ), 481 | Math.round( hue2rgb( p, q, h ) * 255 ), 482 | Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ), 483 | a 484 | ]; 485 | }; 486 | 487 | 488 | each( spaces, function( spaceName, space ) { 489 | var props = space.props, 490 | cache = space.cache, 491 | to = space.to, 492 | from = space.from; 493 | 494 | // makes rgba() and hsla() 495 | color.fn[ spaceName ] = function( value ) { 496 | 497 | // generate a cache for this space if it doesn't exist 498 | if ( to && !this[ cache ] ) { 499 | this[ cache ] = to( this._rgba ); 500 | } 501 | if ( value === undefined ) { 502 | return this[ cache ].slice(); 503 | } 504 | 505 | var type = jQuery.type( value ), 506 | arr = ( type === "array" || type === "object" ) ? value : arguments, 507 | local = this[ cache ].slice(), 508 | ret; 509 | 510 | each( props, function( key, prop ) { 511 | var val = arr[ type === "object" ? key : prop.idx ]; 512 | if ( val == null ) { 513 | val = local[ prop.idx ]; 514 | } 515 | local[ prop.idx ] = clamp( val, prop ); 516 | }); 517 | 518 | if ( from ) { 519 | ret = color( from( local ) ); 520 | ret[ cache ] = local; 521 | return ret; 522 | } else { 523 | return color( local ); 524 | } 525 | }; 526 | 527 | // makes red() green() blue() alpha() hue() saturation() lightness() 528 | each( props, function( key, prop ) { 529 | // alpha is included in more than one space 530 | if ( color.fn[ key ] ) { 531 | return; 532 | } 533 | color.fn[ key ] = function( value ) { 534 | var vtype = jQuery.type( value ), 535 | fn = ( key === 'alpha' ? ( this._hsla ? 'hsla' : 'rgba' ) : spaceName ), 536 | local = this[ fn ](), 537 | cur = local[ prop.idx ], 538 | match; 539 | 540 | if ( vtype === "undefined" ) { 541 | return cur; 542 | } 543 | 544 | if ( vtype === "function" ) { 545 | value = value.call( this, cur ); 546 | vtype = jQuery.type( value ); 547 | } 548 | if ( value == null && prop.empty ) { 549 | return this; 550 | } 551 | if ( vtype === "string" ) { 552 | match = rplusequals.exec( value ); 553 | if ( match ) { 554 | value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 ); 555 | } 556 | } 557 | local[ prop.idx ] = value; 558 | return this[ fn ]( local ); 559 | }; 560 | }); 561 | }); 562 | 563 | // add .fx.step functions 564 | each( stepHooks, function( i, hook ) { 565 | jQuery.cssHooks[ hook ] = { 566 | set: function( elem, value ) { 567 | var parsed, backgroundColor, curElem; 568 | 569 | if ( jQuery.type( value ) !== 'string' || ( parsed = stringParse( value ) ) ) 570 | { 571 | value = color( parsed || value ); 572 | if ( !support.rgba && value._rgba[ 3 ] !== 1 ) { 573 | curElem = hook === "backgroundColor" ? elem.parentNode : elem; 574 | do { 575 | backgroundColor = jQuery.curCSS( curElem, "backgroundColor" ); 576 | } while ( 577 | ( backgroundColor === "" || backgroundColor === "transparent" ) && 578 | ( curElem = curElem.parentNode ) && 579 | curElem.style 580 | ); 581 | 582 | value = value.blend( backgroundColor && backgroundColor !== "transparent" ? 583 | backgroundColor : 584 | "_default" ); 585 | } 586 | 587 | value = value.toRgbaString(); 588 | } 589 | elem.style[ hook ] = value; 590 | } 591 | }; 592 | jQuery.fx.step[ hook ] = function( fx ) { 593 | if ( !fx.colorInit ) { 594 | fx.start = color( fx.elem, hook ); 595 | fx.end = color( fx.end ); 596 | fx.colorInit = true; 597 | } 598 | jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) ); 599 | }; 600 | }); 601 | 602 | // detect rgba support 603 | jQuery(function() { 604 | var div = document.createElement( "div" ), 605 | div_style = div.style; 606 | 607 | div_style.cssText = "background-color:rgba(1,1,1,.5)"; 608 | support.rgba = div_style.backgroundColor.indexOf( "rgba" ) > -1; 609 | }); 610 | 611 | // Some named colors to work with 612 | // From Interface by Stefan Petre 613 | // http://interface.eyecon.ro/ 614 | colors = jQuery.Color.names = { 615 | aqua: "#00ffff", 616 | azure: "#f0ffff", 617 | beige: "#f5f5dc", 618 | black: "#000000", 619 | blue: "#0000ff", 620 | brown: "#a52a2a", 621 | cyan: "#00ffff", 622 | darkblue: "#00008b", 623 | darkcyan: "#008b8b", 624 | darkgrey: "#a9a9a9", 625 | darkgreen: "#006400", 626 | darkkhaki: "#bdb76b", 627 | darkmagenta: "#8b008b", 628 | darkolivegreen: "#556b2f", 629 | darkorange: "#ff8c00", 630 | darkorchid: "#9932cc", 631 | darkred: "#8b0000", 632 | darksalmon: "#e9967a", 633 | darkviolet: "#9400d3", 634 | fuchsia: "#ff00ff", 635 | gold: "#ffd700", 636 | green: "#008000", 637 | indigo: "#4b0082", 638 | khaki: "#f0e68c", 639 | lightblue: "#add8e6", 640 | lightcyan: "#e0ffff", 641 | lightgreen: "#90ee90", 642 | lightgrey: "#d3d3d3", 643 | lightpink: "#ffb6c1", 644 | lightyellow: "#ffffe0", 645 | lime: "#00ff00", 646 | magenta: "#ff00ff", 647 | maroon: "#800000", 648 | navy: "#000080", 649 | olive: "#808000", 650 | orange: "#ffa500", 651 | pink: "#ffc0cb", 652 | purple: "#800080", 653 | violet: "#800080", 654 | red: "#ff0000", 655 | silver: "#c0c0c0", 656 | white: "#ffffff", 657 | yellow: "#ffff00", 658 | transparent: [ null, null, null, 0 ], 659 | _default: "#ffffff" 660 | }; 661 | })( jQuery ); 662 | -------------------------------------------------------------------------------- /imagekit_cropper/static/imagekit_cropper/jcrop/js/jquery.Jcrop.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jquery.Jcrop.js v0.9.12 3 | * jQuery Image Cropping Plugin - released under MIT License 4 | * Author: Kelly Hallman 5 | * http://github.com/tapmodo/Jcrop 6 | * Copyright (c) 2008-2013 Tapmodo Interactive LLC {{{ 7 | * 8 | * Permission is hereby granted, free of charge, to any person 9 | * obtaining a copy of this software and associated documentation 10 | * files (the "Software"), to deal in the Software without 11 | * restriction, including without limitation the rights to use, 12 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the 14 | * Software is furnished to do so, subject to the following 15 | * conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * }}} 30 | */ 31 | 32 | (function ($) { 33 | 34 | $.Jcrop = function (obj, opt) { 35 | var options = $.extend({}, $.Jcrop.defaults), 36 | docOffset, 37 | _ua = navigator.userAgent.toLowerCase(), 38 | is_msie = /msie/.test(_ua), 39 | ie6mode = /msie [1-6]\./.test(_ua); 40 | 41 | // Internal Methods {{{ 42 | function px(n) { 43 | return Math.round(n) + 'px'; 44 | } 45 | function cssClass(cl) { 46 | return options.baseClass + '-' + cl; 47 | } 48 | function supportsColorFade() { 49 | return $.fx.step.hasOwnProperty('backgroundColor'); 50 | } 51 | function getPos(obj) //{{{ 52 | { 53 | var pos = $(obj).offset(); 54 | return [pos.left, pos.top]; 55 | } 56 | //}}} 57 | function mouseAbs(e) //{{{ 58 | { 59 | return [(e.pageX - docOffset[0]), (e.pageY - docOffset[1])]; 60 | } 61 | //}}} 62 | function setOptions(opt) //{{{ 63 | { 64 | if (typeof(opt) !== 'object') opt = {}; 65 | options = $.extend(options, opt); 66 | 67 | $.each(['onChange','onSelect','onRelease','onDblClick'],function(i,e) { 68 | if (typeof(options[e]) !== 'function') options[e] = function () {}; 69 | }); 70 | } 71 | //}}} 72 | function startDragMode(mode, pos, touch) //{{{ 73 | { 74 | docOffset = getPos($img); 75 | Tracker.setCursor(mode === 'move' ? mode : mode + '-resize'); 76 | 77 | if (mode === 'move') { 78 | return Tracker.activateHandlers(createMover(pos), doneSelect, touch); 79 | } 80 | 81 | var fc = Coords.getFixed(); 82 | var opp = oppLockCorner(mode); 83 | var opc = Coords.getCorner(oppLockCorner(opp)); 84 | 85 | Coords.setPressed(Coords.getCorner(opp)); 86 | Coords.setCurrent(opc); 87 | 88 | Tracker.activateHandlers(dragmodeHandler(mode, fc), doneSelect, touch); 89 | } 90 | //}}} 91 | function dragmodeHandler(mode, f) //{{{ 92 | { 93 | return function (pos) { 94 | if (!options.aspectRatio) { 95 | switch (mode) { 96 | case 'e': 97 | pos[1] = f.y2; 98 | break; 99 | case 'w': 100 | pos[1] = f.y2; 101 | break; 102 | case 'n': 103 | pos[0] = f.x2; 104 | break; 105 | case 's': 106 | pos[0] = f.x2; 107 | break; 108 | } 109 | } else { 110 | switch (mode) { 111 | case 'e': 112 | pos[1] = f.y + 1; 113 | break; 114 | case 'w': 115 | pos[1] = f.y + 1; 116 | break; 117 | case 'n': 118 | pos[0] = f.x + 1; 119 | break; 120 | case 's': 121 | pos[0] = f.x + 1; 122 | break; 123 | } 124 | } 125 | Coords.setCurrent(pos); 126 | Selection.update(); 127 | }; 128 | } 129 | //}}} 130 | function createMover(pos) //{{{ 131 | { 132 | var lloc = pos; 133 | KeyManager.watchKeys(); 134 | 135 | return function (pos) { 136 | Coords.moveOffset([pos[0] - lloc[0], pos[1] - lloc[1]]); 137 | lloc = pos; 138 | 139 | Selection.update(); 140 | }; 141 | } 142 | //}}} 143 | function oppLockCorner(ord) //{{{ 144 | { 145 | switch (ord) { 146 | case 'n': 147 | return 'sw'; 148 | case 's': 149 | return 'nw'; 150 | case 'e': 151 | return 'nw'; 152 | case 'w': 153 | return 'ne'; 154 | case 'ne': 155 | return 'sw'; 156 | case 'nw': 157 | return 'se'; 158 | case 'se': 159 | return 'nw'; 160 | case 'sw': 161 | return 'ne'; 162 | } 163 | } 164 | //}}} 165 | function createDragger(ord) //{{{ 166 | { 167 | return function (e) { 168 | if (options.disabled) { 169 | return false; 170 | } 171 | if ((ord === 'move') && !options.allowMove) { 172 | return false; 173 | } 174 | 175 | // Fix position of crop area when dragged the very first time. 176 | // Necessary when crop image is in a hidden element when page is loaded. 177 | docOffset = getPos($img); 178 | 179 | btndown = true; 180 | startDragMode(ord, mouseAbs(e)); 181 | e.stopPropagation(); 182 | e.preventDefault(); 183 | return false; 184 | }; 185 | } 186 | //}}} 187 | function presize($obj, w, h) //{{{ 188 | { 189 | var nw = $obj.width(), 190 | nh = $obj.height(); 191 | if ((nw > w) && w > 0) { 192 | nw = w; 193 | nh = (w / $obj.width()) * $obj.height(); 194 | } 195 | if ((nh > h) && h > 0) { 196 | nh = h; 197 | nw = (h / $obj.height()) * $obj.width(); 198 | } 199 | xscale = $obj.width() / nw; 200 | yscale = $obj.height() / nh; 201 | $obj.width(nw).height(nh); 202 | } 203 | //}}} 204 | function unscale(c) //{{{ 205 | { 206 | return { 207 | x: c.x * xscale, 208 | y: c.y * yscale, 209 | x2: c.x2 * xscale, 210 | y2: c.y2 * yscale, 211 | w: c.w * xscale, 212 | h: c.h * yscale 213 | }; 214 | } 215 | //}}} 216 | function doneSelect(pos) //{{{ 217 | { 218 | var c = Coords.getFixed(); 219 | if ((c.w > options.minSelect[0]) && (c.h > options.minSelect[1])) { 220 | Selection.enableHandles(); 221 | Selection.done(); 222 | } else { 223 | Selection.release(); 224 | } 225 | Tracker.setCursor(options.allowSelect ? 'crosshair' : 'default'); 226 | } 227 | //}}} 228 | function newSelection(e) //{{{ 229 | { 230 | if (options.disabled) { 231 | return false; 232 | } 233 | if (!options.allowSelect) { 234 | return false; 235 | } 236 | btndown = true; 237 | docOffset = getPos($img); 238 | Selection.disableHandles(); 239 | Tracker.setCursor('crosshair'); 240 | var pos = mouseAbs(e); 241 | Coords.setPressed(pos); 242 | Selection.update(); 243 | Tracker.activateHandlers(selectDrag, doneSelect, e.type.substring(0,5)==='touch'); 244 | KeyManager.watchKeys(); 245 | 246 | e.stopPropagation(); 247 | e.preventDefault(); 248 | return false; 249 | } 250 | //}}} 251 | function selectDrag(pos) //{{{ 252 | { 253 | Coords.setCurrent(pos); 254 | Selection.update(); 255 | } 256 | //}}} 257 | function newTracker() //{{{ 258 | { 259 | var trk = $('
').addClass(cssClass('tracker')); 260 | if (is_msie) { 261 | trk.css({ 262 | opacity: 0, 263 | backgroundColor: 'white' 264 | }); 265 | } 266 | return trk; 267 | } 268 | //}}} 269 | 270 | // }}} 271 | // Initialization {{{ 272 | // Sanitize some options {{{ 273 | if (typeof(obj) !== 'object') { 274 | obj = $(obj)[0]; 275 | } 276 | if (typeof(opt) !== 'object') { 277 | opt = {}; 278 | } 279 | // }}} 280 | setOptions(opt); 281 | // Initialize some jQuery objects {{{ 282 | // The values are SET on the image(s) for the interface 283 | // If the original image has any of these set, they will be reset 284 | // However, if you destroy() the Jcrop instance the original image's 285 | // character in the DOM will be as you left it. 286 | var img_css = { 287 | border: 'none', 288 | visibility: 'visible', 289 | margin: 0, 290 | padding: 0, 291 | position: 'absolute', 292 | top: 0, 293 | left: 0 294 | }; 295 | 296 | var $origimg = $(obj), 297 | img_mode = true; 298 | 299 | if (obj.tagName == 'IMG') { 300 | // Fix size of crop image. 301 | // Necessary when crop image is within a hidden element when page is loaded. 302 | if ($origimg[0].width != 0 && $origimg[0].height != 0) { 303 | // Obtain dimensions from contained img element. 304 | $origimg.width($origimg[0].width); 305 | $origimg.height($origimg[0].height); 306 | } else { 307 | // Obtain dimensions from temporary image in case the original is not loaded yet (e.g. IE 7.0). 308 | var tempImage = new Image(); 309 | tempImage.src = $origimg[0].src; 310 | $origimg.width(tempImage.width); 311 | $origimg.height(tempImage.height); 312 | } 313 | 314 | var $img = $origimg.clone().removeAttr('id').css(img_css).show(); 315 | 316 | $img.width($origimg.width()); 317 | $img.height($origimg.height()); 318 | $origimg.after($img).hide(); 319 | 320 | } else { 321 | $img = $origimg.css(img_css).show(); 322 | img_mode = false; 323 | if (options.shade === null) { options.shade = true; } 324 | } 325 | 326 | presize($img, options.boxWidth, options.boxHeight); 327 | 328 | var boundx = $img.width(), 329 | boundy = $img.height(), 330 | 331 | 332 | $div = $('
').width(boundx).height(boundy).addClass(cssClass('holder')).css({ 333 | position: 'relative', 334 | backgroundColor: options.bgColor 335 | }).insertAfter($origimg).append($img); 336 | 337 | if (options.addClass) { 338 | $div.addClass(options.addClass); 339 | } 340 | 341 | var $img2 = $('
'), 342 | 343 | $img_holder = $('
') 344 | .width('100%').height('100%').css({ 345 | zIndex: 310, 346 | position: 'absolute', 347 | overflow: 'hidden' 348 | }), 349 | 350 | $hdl_holder = $('
') 351 | .width('100%').height('100%').css('zIndex', 320), 352 | 353 | $sel = $('
') 354 | .css({ 355 | position: 'absolute', 356 | zIndex: 600 357 | }).dblclick(function(){ 358 | var c = Coords.getFixed(); 359 | options.onDblClick.call(api,c); 360 | }).insertBefore($img).append($img_holder, $hdl_holder); 361 | 362 | if (img_mode) { 363 | 364 | $img2 = $('') 365 | .attr('src', $img.attr('src')).css(img_css).width(boundx).height(boundy), 366 | 367 | $img_holder.append($img2); 368 | 369 | } 370 | 371 | if (ie6mode) { 372 | $sel.css({ 373 | overflowY: 'hidden' 374 | }); 375 | } 376 | 377 | var bound = options.boundary; 378 | var $trk = newTracker().width(boundx + (bound * 2)).height(boundy + (bound * 2)).css({ 379 | position: 'absolute', 380 | top: px(-bound), 381 | left: px(-bound), 382 | zIndex: 290 383 | }).mousedown(newSelection); 384 | 385 | /* }}} */ 386 | // Set more variables {{{ 387 | var bgcolor = options.bgColor, 388 | bgopacity = options.bgOpacity, 389 | xlimit, ylimit, xmin, ymin, xscale, yscale, enabled = true, 390 | btndown, animating, shift_down; 391 | 392 | docOffset = getPos($img); 393 | // }}} 394 | // }}} 395 | // Internal Modules {{{ 396 | // Touch Module {{{ 397 | var Touch = (function () { 398 | // Touch support detection function adapted (under MIT License) 399 | // from code by Jeffrey Sambells - http://github.com/iamamused/ 400 | function hasTouchSupport() { 401 | var support = {}, events = ['touchstart', 'touchmove', 'touchend'], 402 | el = document.createElement('div'), i; 403 | 404 | try { 405 | for(i=0; i x1 + ox) { 491 | ox -= ox + x1; 492 | } 493 | if (0 > y1 + oy) { 494 | oy -= oy + y1; 495 | } 496 | 497 | if (boundy < y2 + oy) { 498 | oy += boundy - (y2 + oy); 499 | } 500 | if (boundx < x2 + ox) { 501 | ox += boundx - (x2 + ox); 502 | } 503 | 504 | x1 += ox; 505 | x2 += ox; 506 | y1 += oy; 507 | y2 += oy; 508 | } 509 | //}}} 510 | function getCorner(ord) //{{{ 511 | { 512 | var c = getFixed(); 513 | switch (ord) { 514 | case 'ne': 515 | return [c.x2, c.y]; 516 | case 'nw': 517 | return [c.x, c.y]; 518 | case 'se': 519 | return [c.x2, c.y2]; 520 | case 'sw': 521 | return [c.x, c.y2]; 522 | } 523 | } 524 | //}}} 525 | function getFixed() //{{{ 526 | { 527 | if (!options.aspectRatio) { 528 | return getRect(); 529 | } 530 | // This function could use some optimization I think... 531 | var aspect = options.aspectRatio, 532 | min_x = options.minSize[0] / xscale, 533 | 534 | 535 | //min_y = options.minSize[1]/yscale, 536 | max_x = options.maxSize[0] / xscale, 537 | max_y = options.maxSize[1] / yscale, 538 | rw = x2 - x1, 539 | rh = y2 - y1, 540 | rwa = Math.abs(rw), 541 | rha = Math.abs(rh), 542 | real_ratio = rwa / rha, 543 | xx, yy, w, h; 544 | 545 | if (max_x === 0) { 546 | max_x = boundx * 10; 547 | } 548 | if (max_y === 0) { 549 | max_y = boundy * 10; 550 | } 551 | if (real_ratio < aspect) { 552 | yy = y2; 553 | w = rha * aspect; 554 | xx = rw < 0 ? x1 - w : w + x1; 555 | 556 | if (xx < 0) { 557 | xx = 0; 558 | h = Math.abs((xx - x1) / aspect); 559 | yy = rh < 0 ? y1 - h : h + y1; 560 | } else if (xx > boundx) { 561 | xx = boundx; 562 | h = Math.abs((xx - x1) / aspect); 563 | yy = rh < 0 ? y1 - h : h + y1; 564 | } 565 | } else { 566 | xx = x2; 567 | h = rwa / aspect; 568 | yy = rh < 0 ? y1 - h : y1 + h; 569 | if (yy < 0) { 570 | yy = 0; 571 | w = Math.abs((yy - y1) * aspect); 572 | xx = rw < 0 ? x1 - w : w + x1; 573 | } else if (yy > boundy) { 574 | yy = boundy; 575 | w = Math.abs(yy - y1) * aspect; 576 | xx = rw < 0 ? x1 - w : w + x1; 577 | } 578 | } 579 | 580 | // Magic %-) 581 | if (xx > x1) { // right side 582 | if (xx - x1 < min_x) { 583 | xx = x1 + min_x; 584 | } else if (xx - x1 > max_x) { 585 | xx = x1 + max_x; 586 | } 587 | if (yy > y1) { 588 | yy = y1 + (xx - x1) / aspect; 589 | } else { 590 | yy = y1 - (xx - x1) / aspect; 591 | } 592 | } else if (xx < x1) { // left side 593 | if (x1 - xx < min_x) { 594 | xx = x1 - min_x; 595 | } else if (x1 - xx > max_x) { 596 | xx = x1 - max_x; 597 | } 598 | if (yy > y1) { 599 | yy = y1 + (x1 - xx) / aspect; 600 | } else { 601 | yy = y1 - (x1 - xx) / aspect; 602 | } 603 | } 604 | 605 | if (xx < 0) { 606 | x1 -= xx; 607 | xx = 0; 608 | } else if (xx > boundx) { 609 | x1 -= xx - boundx; 610 | xx = boundx; 611 | } 612 | 613 | if (yy < 0) { 614 | y1 -= yy; 615 | yy = 0; 616 | } else if (yy > boundy) { 617 | y1 -= yy - boundy; 618 | yy = boundy; 619 | } 620 | 621 | return makeObj(flipCoords(x1, y1, xx, yy)); 622 | } 623 | //}}} 624 | function rebound(p) //{{{ 625 | { 626 | if (p[0] < 0) p[0] = 0; 627 | if (p[1] < 0) p[1] = 0; 628 | 629 | if (p[0] > boundx) p[0] = boundx; 630 | if (p[1] > boundy) p[1] = boundy; 631 | 632 | return [Math.round(p[0]), Math.round(p[1])]; 633 | } 634 | //}}} 635 | function flipCoords(x1, y1, x2, y2) //{{{ 636 | { 637 | var xa = x1, 638 | xb = x2, 639 | ya = y1, 640 | yb = y2; 641 | if (x2 < x1) { 642 | xa = x2; 643 | xb = x1; 644 | } 645 | if (y2 < y1) { 646 | ya = y2; 647 | yb = y1; 648 | } 649 | return [xa, ya, xb, yb]; 650 | } 651 | //}}} 652 | function getRect() //{{{ 653 | { 654 | var xsize = x2 - x1, 655 | ysize = y2 - y1, 656 | delta; 657 | 658 | if (xlimit && (Math.abs(xsize) > xlimit)) { 659 | x2 = (xsize > 0) ? (x1 + xlimit) : (x1 - xlimit); 660 | } 661 | if (ylimit && (Math.abs(ysize) > ylimit)) { 662 | y2 = (ysize > 0) ? (y1 + ylimit) : (y1 - ylimit); 663 | } 664 | 665 | if (ymin / yscale && (Math.abs(ysize) < ymin / yscale)) { 666 | y2 = (ysize > 0) ? (y1 + ymin / yscale) : (y1 - ymin / yscale); 667 | } 668 | if (xmin / xscale && (Math.abs(xsize) < xmin / xscale)) { 669 | x2 = (xsize > 0) ? (x1 + xmin / xscale) : (x1 - xmin / xscale); 670 | } 671 | 672 | if (x1 < 0) { 673 | x2 -= x1; 674 | x1 -= x1; 675 | } 676 | if (y1 < 0) { 677 | y2 -= y1; 678 | y1 -= y1; 679 | } 680 | if (x2 < 0) { 681 | x1 -= x2; 682 | x2 -= x2; 683 | } 684 | if (y2 < 0) { 685 | y1 -= y2; 686 | y2 -= y2; 687 | } 688 | if (x2 > boundx) { 689 | delta = x2 - boundx; 690 | x1 -= delta; 691 | x2 -= delta; 692 | } 693 | if (y2 > boundy) { 694 | delta = y2 - boundy; 695 | y1 -= delta; 696 | y2 -= delta; 697 | } 698 | if (x1 > boundx) { 699 | delta = x1 - boundy; 700 | y2 -= delta; 701 | y1 -= delta; 702 | } 703 | if (y1 > boundy) { 704 | delta = y1 - boundy; 705 | y2 -= delta; 706 | y1 -= delta; 707 | } 708 | 709 | return makeObj(flipCoords(x1, y1, x2, y2)); 710 | } 711 | //}}} 712 | function makeObj(a) //{{{ 713 | { 714 | return { 715 | x: a[0], 716 | y: a[1], 717 | x2: a[2], 718 | y2: a[3], 719 | w: a[2] - a[0], 720 | h: a[3] - a[1] 721 | }; 722 | } 723 | //}}} 724 | 725 | return { 726 | flipCoords: flipCoords, 727 | setPressed: setPressed, 728 | setCurrent: setCurrent, 729 | getOffset: getOffset, 730 | moveOffset: moveOffset, 731 | getCorner: getCorner, 732 | getFixed: getFixed 733 | }; 734 | }()); 735 | 736 | //}}} 737 | // Shade Module {{{ 738 | var Shade = (function() { 739 | var enabled = false, 740 | holder = $('
').css({ 741 | position: 'absolute', 742 | zIndex: 240, 743 | opacity: 0 744 | }), 745 | shades = { 746 | top: createShade(), 747 | left: createShade().height(boundy), 748 | right: createShade().height(boundy), 749 | bottom: createShade() 750 | }; 751 | 752 | function resizeShades(w,h) { 753 | shades.left.css({ height: px(h) }); 754 | shades.right.css({ height: px(h) }); 755 | } 756 | function updateAuto() 757 | { 758 | return updateShade(Coords.getFixed()); 759 | } 760 | function updateShade(c) 761 | { 762 | shades.top.css({ 763 | left: px(c.x), 764 | width: px(c.w), 765 | height: px(c.y) 766 | }); 767 | shades.bottom.css({ 768 | top: px(c.y2), 769 | left: px(c.x), 770 | width: px(c.w), 771 | height: px(boundy-c.y2) 772 | }); 773 | shades.right.css({ 774 | left: px(c.x2), 775 | width: px(boundx-c.x2) 776 | }); 777 | shades.left.css({ 778 | width: px(c.x) 779 | }); 780 | } 781 | function createShade() { 782 | return $('
').css({ 783 | position: 'absolute', 784 | backgroundColor: options.shadeColor||options.bgColor 785 | }).appendTo(holder); 786 | } 787 | function enableShade() { 788 | if (!enabled) { 789 | enabled = true; 790 | holder.insertBefore($img); 791 | updateAuto(); 792 | Selection.setBgOpacity(1,0,1); 793 | $img2.hide(); 794 | 795 | setBgColor(options.shadeColor||options.bgColor,1); 796 | if (Selection.isAwake()) 797 | { 798 | setOpacity(options.bgOpacity,1); 799 | } 800 | else setOpacity(1,1); 801 | } 802 | } 803 | function setBgColor(color,now) { 804 | colorChangeMacro(getShades(),color,now); 805 | } 806 | function disableShade() { 807 | if (enabled) { 808 | holder.remove(); 809 | $img2.show(); 810 | enabled = false; 811 | if (Selection.isAwake()) { 812 | Selection.setBgOpacity(options.bgOpacity,1,1); 813 | } else { 814 | Selection.setBgOpacity(1,1,1); 815 | Selection.disableHandles(); 816 | } 817 | colorChangeMacro($div,0,1); 818 | } 819 | } 820 | function setOpacity(opacity,now) { 821 | if (enabled) { 822 | if (options.bgFade && !now) { 823 | holder.animate({ 824 | opacity: 1-opacity 825 | },{ 826 | queue: false, 827 | duration: options.fadeTime 828 | }); 829 | } 830 | else holder.css({opacity:1-opacity}); 831 | } 832 | } 833 | function refreshAll() { 834 | options.shade ? enableShade() : disableShade(); 835 | if (Selection.isAwake()) setOpacity(options.bgOpacity); 836 | } 837 | function getShades() { 838 | return holder.children(); 839 | } 840 | 841 | return { 842 | update: updateAuto, 843 | updateRaw: updateShade, 844 | getShades: getShades, 845 | setBgColor: setBgColor, 846 | enable: enableShade, 847 | disable: disableShade, 848 | resize: resizeShades, 849 | refresh: refreshAll, 850 | opacity: setOpacity 851 | }; 852 | }()); 853 | // }}} 854 | // Selection Module {{{ 855 | var Selection = (function () { 856 | var awake, 857 | hdep = 370, 858 | borders = {}, 859 | handle = {}, 860 | dragbar = {}, 861 | seehandles = false; 862 | 863 | // Private Methods 864 | function insertBorder(type) //{{{ 865 | { 866 | var jq = $('
').css({ 867 | position: 'absolute', 868 | opacity: options.borderOpacity 869 | }).addClass(cssClass(type)); 870 | $img_holder.append(jq); 871 | return jq; 872 | } 873 | //}}} 874 | function dragDiv(ord, zi) //{{{ 875 | { 876 | var jq = $('
').mousedown(createDragger(ord)).css({ 877 | cursor: ord + '-resize', 878 | position: 'absolute', 879 | zIndex: zi 880 | }).addClass('ord-'+ord); 881 | 882 | if (Touch.support) { 883 | jq.bind('touchstart.jcrop', Touch.createDragger(ord)); 884 | } 885 | 886 | $hdl_holder.append(jq); 887 | return jq; 888 | } 889 | //}}} 890 | function insertHandle(ord) //{{{ 891 | { 892 | var hs = options.handleSize, 893 | 894 | div = dragDiv(ord, hdep++).css({ 895 | opacity: options.handleOpacity 896 | }).addClass(cssClass('handle')); 897 | 898 | if (hs) { div.width(hs).height(hs); } 899 | 900 | return div; 901 | } 902 | //}}} 903 | function insertDragbar(ord) //{{{ 904 | { 905 | return dragDiv(ord, hdep++).addClass('jcrop-dragbar'); 906 | } 907 | //}}} 908 | function createDragbars(li) //{{{ 909 | { 910 | var i; 911 | for (i = 0; i < li.length; i++) { 912 | dragbar[li[i]] = insertDragbar(li[i]); 913 | } 914 | } 915 | //}}} 916 | function createBorders(li) //{{{ 917 | { 918 | var cl,i; 919 | for (i = 0; i < li.length; i++) { 920 | switch(li[i]){ 921 | case'n': cl='hline'; break; 922 | case's': cl='hline bottom'; break; 923 | case'e': cl='vline right'; break; 924 | case'w': cl='vline'; break; 925 | } 926 | borders[li[i]] = insertBorder(cl); 927 | } 928 | } 929 | //}}} 930 | function createHandles(li) //{{{ 931 | { 932 | var i; 933 | for (i = 0; i < li.length; i++) { 934 | handle[li[i]] = insertHandle(li[i]); 935 | } 936 | } 937 | //}}} 938 | function moveto(x, y) //{{{ 939 | { 940 | if (!options.shade) { 941 | $img2.css({ 942 | top: px(-y), 943 | left: px(-x) 944 | }); 945 | } 946 | $sel.css({ 947 | top: px(y), 948 | left: px(x) 949 | }); 950 | } 951 | //}}} 952 | function resize(w, h) //{{{ 953 | { 954 | $sel.width(Math.round(w)).height(Math.round(h)); 955 | } 956 | //}}} 957 | function refresh() //{{{ 958 | { 959 | var c = Coords.getFixed(); 960 | 961 | Coords.setPressed([c.x, c.y]); 962 | Coords.setCurrent([c.x2, c.y2]); 963 | 964 | updateVisible(); 965 | } 966 | //}}} 967 | 968 | // Internal Methods 969 | function updateVisible(select) //{{{ 970 | { 971 | if (awake) { 972 | return update(select); 973 | } 974 | } 975 | //}}} 976 | function update(select) //{{{ 977 | { 978 | var c = Coords.getFixed(); 979 | 980 | resize(c.w, c.h); 981 | moveto(c.x, c.y); 982 | if (options.shade) Shade.updateRaw(c); 983 | 984 | awake || show(); 985 | 986 | if (select) { 987 | options.onSelect.call(api, unscale(c)); 988 | } else { 989 | options.onChange.call(api, unscale(c)); 990 | } 991 | } 992 | //}}} 993 | function setBgOpacity(opacity,force,now) //{{{ 994 | { 995 | if (!awake && !force) return; 996 | if (options.bgFade && !now) { 997 | $img.animate({ 998 | opacity: opacity 999 | },{ 1000 | queue: false, 1001 | duration: options.fadeTime 1002 | }); 1003 | } else { 1004 | $img.css('opacity', opacity); 1005 | } 1006 | } 1007 | //}}} 1008 | function show() //{{{ 1009 | { 1010 | $sel.show(); 1011 | 1012 | if (options.shade) Shade.opacity(bgopacity); 1013 | else setBgOpacity(bgopacity,true); 1014 | 1015 | awake = true; 1016 | } 1017 | //}}} 1018 | function release() //{{{ 1019 | { 1020 | disableHandles(); 1021 | $sel.hide(); 1022 | 1023 | if (options.shade) Shade.opacity(1); 1024 | else setBgOpacity(1); 1025 | 1026 | awake = false; 1027 | options.onRelease.call(api); 1028 | } 1029 | //}}} 1030 | function showHandles() //{{{ 1031 | { 1032 | if (seehandles) { 1033 | $hdl_holder.show(); 1034 | } 1035 | } 1036 | //}}} 1037 | function enableHandles() //{{{ 1038 | { 1039 | seehandles = true; 1040 | if (options.allowResize) { 1041 | $hdl_holder.show(); 1042 | return true; 1043 | } 1044 | } 1045 | //}}} 1046 | function disableHandles() //{{{ 1047 | { 1048 | seehandles = false; 1049 | $hdl_holder.hide(); 1050 | } 1051 | //}}} 1052 | function animMode(v) //{{{ 1053 | { 1054 | if (v) { 1055 | animating = true; 1056 | disableHandles(); 1057 | } else { 1058 | animating = false; 1059 | enableHandles(); 1060 | } 1061 | } 1062 | //}}} 1063 | function done() //{{{ 1064 | { 1065 | animMode(false); 1066 | refresh(); 1067 | } 1068 | //}}} 1069 | // Insert draggable elements {{{ 1070 | // Insert border divs for outline 1071 | 1072 | if (options.dragEdges && $.isArray(options.createDragbars)) 1073 | createDragbars(options.createDragbars); 1074 | 1075 | if ($.isArray(options.createHandles)) 1076 | createHandles(options.createHandles); 1077 | 1078 | if (options.drawBorders && $.isArray(options.createBorders)) 1079 | createBorders(options.createBorders); 1080 | 1081 | //}}} 1082 | 1083 | // This is a hack for iOS5 to support drag/move touch functionality 1084 | $(document).bind('touchstart.jcrop-ios',function(e) { 1085 | if ($(e.currentTarget).hasClass('jcrop-tracker')) e.stopPropagation(); 1086 | }); 1087 | 1088 | var $track = newTracker().mousedown(createDragger('move')).css({ 1089 | cursor: 'move', 1090 | position: 'absolute', 1091 | zIndex: 360 1092 | }); 1093 | 1094 | if (Touch.support) { 1095 | $track.bind('touchstart.jcrop', Touch.createDragger('move')); 1096 | } 1097 | 1098 | $img_holder.append($track); 1099 | disableHandles(); 1100 | 1101 | return { 1102 | updateVisible: updateVisible, 1103 | update: update, 1104 | release: release, 1105 | refresh: refresh, 1106 | isAwake: function () { 1107 | return awake; 1108 | }, 1109 | setCursor: function (cursor) { 1110 | $track.css('cursor', cursor); 1111 | }, 1112 | enableHandles: enableHandles, 1113 | enableOnly: function () { 1114 | seehandles = true; 1115 | }, 1116 | showHandles: showHandles, 1117 | disableHandles: disableHandles, 1118 | animMode: animMode, 1119 | setBgOpacity: setBgOpacity, 1120 | done: done 1121 | }; 1122 | }()); 1123 | 1124 | //}}} 1125 | // Tracker Module {{{ 1126 | var Tracker = (function () { 1127 | var onMove = function () {}, 1128 | onDone = function () {}, 1129 | trackDoc = options.trackDocument; 1130 | 1131 | function toFront(touch) //{{{ 1132 | { 1133 | $trk.css({ 1134 | zIndex: 450 1135 | }); 1136 | 1137 | if (touch) 1138 | $(document) 1139 | .bind('touchmove.jcrop', trackTouchMove) 1140 | .bind('touchend.jcrop', trackTouchEnd); 1141 | 1142 | else if (trackDoc) 1143 | $(document) 1144 | .bind('mousemove.jcrop',trackMove) 1145 | .bind('mouseup.jcrop',trackUp); 1146 | } 1147 | //}}} 1148 | function toBack() //{{{ 1149 | { 1150 | $trk.css({ 1151 | zIndex: 290 1152 | }); 1153 | $(document).unbind('.jcrop'); 1154 | } 1155 | //}}} 1156 | function trackMove(e) //{{{ 1157 | { 1158 | onMove(mouseAbs(e)); 1159 | return false; 1160 | } 1161 | //}}} 1162 | function trackUp(e) //{{{ 1163 | { 1164 | e.preventDefault(); 1165 | e.stopPropagation(); 1166 | 1167 | if (btndown) { 1168 | btndown = false; 1169 | 1170 | onDone(mouseAbs(e)); 1171 | 1172 | if (Selection.isAwake()) { 1173 | options.onSelect.call(api, unscale(Coords.getFixed())); 1174 | } 1175 | 1176 | toBack(); 1177 | onMove = function () {}; 1178 | onDone = function () {}; 1179 | } 1180 | 1181 | return false; 1182 | } 1183 | //}}} 1184 | function activateHandlers(move, done, touch) //{{{ 1185 | { 1186 | btndown = true; 1187 | onMove = move; 1188 | onDone = done; 1189 | toFront(touch); 1190 | return false; 1191 | } 1192 | //}}} 1193 | function trackTouchMove(e) //{{{ 1194 | { 1195 | onMove(mouseAbs(Touch.cfilter(e))); 1196 | return false; 1197 | } 1198 | //}}} 1199 | function trackTouchEnd(e) //{{{ 1200 | { 1201 | return trackUp(Touch.cfilter(e)); 1202 | } 1203 | //}}} 1204 | function setCursor(t) //{{{ 1205 | { 1206 | $trk.css('cursor', t); 1207 | } 1208 | //}}} 1209 | 1210 | if (!trackDoc) { 1211 | $trk.mousemove(trackMove).mouseup(trackUp).mouseout(trackUp); 1212 | } 1213 | 1214 | $img.before($trk); 1215 | return { 1216 | activateHandlers: activateHandlers, 1217 | setCursor: setCursor 1218 | }; 1219 | }()); 1220 | //}}} 1221 | // KeyManager Module {{{ 1222 | var KeyManager = (function () { 1223 | var $keymgr = $('').css({ 1224 | position: 'fixed', 1225 | left: '-120px', 1226 | width: '12px' 1227 | }).addClass('jcrop-keymgr'), 1228 | 1229 | $keywrap = $('
').css({ 1230 | position: 'absolute', 1231 | overflow: 'hidden' 1232 | }).append($keymgr); 1233 | 1234 | function watchKeys() //{{{ 1235 | { 1236 | if (options.keySupport) { 1237 | $keymgr.show(); 1238 | $keymgr.focus(); 1239 | } 1240 | } 1241 | //}}} 1242 | function onBlur(e) //{{{ 1243 | { 1244 | $keymgr.hide(); 1245 | } 1246 | //}}} 1247 | function doNudge(e, x, y) //{{{ 1248 | { 1249 | if (options.allowMove) { 1250 | Coords.moveOffset([x, y]); 1251 | Selection.updateVisible(true); 1252 | } 1253 | e.preventDefault(); 1254 | e.stopPropagation(); 1255 | } 1256 | //}}} 1257 | function parseKey(e) //{{{ 1258 | { 1259 | if (e.ctrlKey || e.metaKey) { 1260 | return true; 1261 | } 1262 | shift_down = e.shiftKey ? true : false; 1263 | var nudge = shift_down ? 10 : 1; 1264 | 1265 | switch (e.keyCode) { 1266 | case 37: 1267 | doNudge(e, -nudge, 0); 1268 | break; 1269 | case 39: 1270 | doNudge(e, nudge, 0); 1271 | break; 1272 | case 38: 1273 | doNudge(e, 0, -nudge); 1274 | break; 1275 | case 40: 1276 | doNudge(e, 0, nudge); 1277 | break; 1278 | case 27: 1279 | if (options.allowSelect) Selection.release(); 1280 | break; 1281 | case 9: 1282 | return true; 1283 | } 1284 | 1285 | return false; 1286 | } 1287 | //}}} 1288 | 1289 | if (options.keySupport) { 1290 | $keymgr.keydown(parseKey).blur(onBlur); 1291 | if (ie6mode || !options.fixedSupport) { 1292 | $keymgr.css({ 1293 | position: 'absolute', 1294 | left: '-20px' 1295 | }); 1296 | $keywrap.append($keymgr).insertBefore($img); 1297 | } else { 1298 | $keymgr.insertBefore($img); 1299 | } 1300 | } 1301 | 1302 | 1303 | return { 1304 | watchKeys: watchKeys 1305 | }; 1306 | }()); 1307 | //}}} 1308 | // }}} 1309 | // API methods {{{ 1310 | function setClass(cname) //{{{ 1311 | { 1312 | $div.removeClass().addClass(cssClass('holder')).addClass(cname); 1313 | } 1314 | //}}} 1315 | function animateTo(a, callback) //{{{ 1316 | { 1317 | var x1 = a[0] / xscale, 1318 | y1 = a[1] / yscale, 1319 | x2 = a[2] / xscale, 1320 | y2 = a[3] / yscale; 1321 | 1322 | if (animating) { 1323 | return; 1324 | } 1325 | 1326 | var animto = Coords.flipCoords(x1, y1, x2, y2), 1327 | c = Coords.getFixed(), 1328 | initcr = [c.x, c.y, c.x2, c.y2], 1329 | animat = initcr, 1330 | interv = options.animationDelay, 1331 | ix1 = animto[0] - initcr[0], 1332 | iy1 = animto[1] - initcr[1], 1333 | ix2 = animto[2] - initcr[2], 1334 | iy2 = animto[3] - initcr[3], 1335 | pcent = 0, 1336 | velocity = options.swingSpeed; 1337 | 1338 | x1 = animat[0]; 1339 | y1 = animat[1]; 1340 | x2 = animat[2]; 1341 | y2 = animat[3]; 1342 | 1343 | Selection.animMode(true); 1344 | var anim_timer; 1345 | 1346 | function queueAnimator() { 1347 | window.setTimeout(animator, interv); 1348 | } 1349 | var animator = (function () { 1350 | return function () { 1351 | pcent += (100 - pcent) / velocity; 1352 | 1353 | animat[0] = Math.round(x1 + ((pcent / 100) * ix1)); 1354 | animat[1] = Math.round(y1 + ((pcent / 100) * iy1)); 1355 | animat[2] = Math.round(x2 + ((pcent / 100) * ix2)); 1356 | animat[3] = Math.round(y2 + ((pcent / 100) * iy2)); 1357 | 1358 | if (pcent >= 99.8) { 1359 | pcent = 100; 1360 | } 1361 | if (pcent < 100) { 1362 | setSelectRaw(animat); 1363 | queueAnimator(); 1364 | } else { 1365 | Selection.done(); 1366 | Selection.animMode(false); 1367 | if (typeof(callback) === 'function') { 1368 | callback.call(api); 1369 | } 1370 | } 1371 | }; 1372 | }()); 1373 | queueAnimator(); 1374 | } 1375 | //}}} 1376 | function setSelect(rect) //{{{ 1377 | { 1378 | setSelectRaw([rect[0] / xscale, rect[1] / yscale, rect[2] / xscale, rect[3] / yscale]); 1379 | options.onSelect.call(api, unscale(Coords.getFixed())); 1380 | Selection.enableHandles(); 1381 | } 1382 | //}}} 1383 | function setSelectRaw(l) //{{{ 1384 | { 1385 | Coords.setPressed([l[0], l[1]]); 1386 | Coords.setCurrent([l[2], l[3]]); 1387 | Selection.update(); 1388 | } 1389 | //}}} 1390 | function tellSelect() //{{{ 1391 | { 1392 | return unscale(Coords.getFixed()); 1393 | } 1394 | //}}} 1395 | function tellScaled() //{{{ 1396 | { 1397 | return Coords.getFixed(); 1398 | } 1399 | //}}} 1400 | function setOptionsNew(opt) //{{{ 1401 | { 1402 | setOptions(opt); 1403 | interfaceUpdate(); 1404 | } 1405 | //}}} 1406 | function disableCrop() //{{{ 1407 | { 1408 | options.disabled = true; 1409 | Selection.disableHandles(); 1410 | Selection.setCursor('default'); 1411 | Tracker.setCursor('default'); 1412 | } 1413 | //}}} 1414 | function enableCrop() //{{{ 1415 | { 1416 | options.disabled = false; 1417 | interfaceUpdate(); 1418 | } 1419 | //}}} 1420 | function cancelCrop() //{{{ 1421 | { 1422 | Selection.done(); 1423 | Tracker.activateHandlers(null, null); 1424 | } 1425 | //}}} 1426 | function destroy() //{{{ 1427 | { 1428 | $div.remove(); 1429 | $origimg.show(); 1430 | $origimg.css('visibility','visible'); 1431 | $(obj).removeData('Jcrop'); 1432 | } 1433 | //}}} 1434 | function setImage(src, callback) //{{{ 1435 | { 1436 | Selection.release(); 1437 | disableCrop(); 1438 | var img = new Image(); 1439 | img.onload = function () { 1440 | var iw = img.width; 1441 | var ih = img.height; 1442 | var bw = options.boxWidth; 1443 | var bh = options.boxHeight; 1444 | $img.width(iw).height(ih); 1445 | $img.attr('src', src); 1446 | $img2.attr('src', src); 1447 | presize($img, bw, bh); 1448 | boundx = $img.width(); 1449 | boundy = $img.height(); 1450 | $img2.width(boundx).height(boundy); 1451 | $trk.width(boundx + (bound * 2)).height(boundy + (bound * 2)); 1452 | $div.width(boundx).height(boundy); 1453 | Shade.resize(boundx,boundy); 1454 | enableCrop(); 1455 | 1456 | if (typeof(callback) === 'function') { 1457 | callback.call(api); 1458 | } 1459 | }; 1460 | img.src = src; 1461 | } 1462 | //}}} 1463 | function colorChangeMacro($obj,color,now) { 1464 | var mycolor = color || options.bgColor; 1465 | if (options.bgFade && supportsColorFade() && options.fadeTime && !now) { 1466 | $obj.animate({ 1467 | backgroundColor: mycolor 1468 | }, { 1469 | queue: false, 1470 | duration: options.fadeTime 1471 | }); 1472 | } else { 1473 | $obj.css('backgroundColor', mycolor); 1474 | } 1475 | } 1476 | function interfaceUpdate(alt) //{{{ 1477 | // This method tweaks the interface based on options object. 1478 | // Called when options are changed and at end of initialization. 1479 | { 1480 | if (options.allowResize) { 1481 | if (alt) { 1482 | Selection.enableOnly(); 1483 | } else { 1484 | Selection.enableHandles(); 1485 | } 1486 | } else { 1487 | Selection.disableHandles(); 1488 | } 1489 | 1490 | Tracker.setCursor(options.allowSelect ? 'crosshair' : 'default'); 1491 | Selection.setCursor(options.allowMove ? 'move' : 'default'); 1492 | 1493 | if (options.hasOwnProperty('trueSize')) { 1494 | xscale = options.trueSize[0] / boundx; 1495 | yscale = options.trueSize[1] / boundy; 1496 | } 1497 | 1498 | if (options.hasOwnProperty('setSelect')) { 1499 | setSelect(options.setSelect); 1500 | Selection.done(); 1501 | delete(options.setSelect); 1502 | } 1503 | 1504 | Shade.refresh(); 1505 | 1506 | if (options.bgColor != bgcolor) { 1507 | colorChangeMacro( 1508 | options.shade? Shade.getShades(): $div, 1509 | options.shade? 1510 | (options.shadeColor || options.bgColor): 1511 | options.bgColor 1512 | ); 1513 | bgcolor = options.bgColor; 1514 | } 1515 | 1516 | if (bgopacity != options.bgOpacity) { 1517 | bgopacity = options.bgOpacity; 1518 | if (options.shade) Shade.refresh(); 1519 | else Selection.setBgOpacity(bgopacity); 1520 | } 1521 | 1522 | xlimit = options.maxSize[0] || 0; 1523 | ylimit = options.maxSize[1] || 0; 1524 | xmin = options.minSize[0] || 0; 1525 | ymin = options.minSize[1] || 0; 1526 | 1527 | if (options.hasOwnProperty('outerImage')) { 1528 | $img.attr('src', options.outerImage); 1529 | delete(options.outerImage); 1530 | } 1531 | 1532 | Selection.refresh(); 1533 | } 1534 | //}}} 1535 | //}}} 1536 | 1537 | if (Touch.support) $trk.bind('touchstart.jcrop', Touch.newSelection); 1538 | 1539 | $hdl_holder.hide(); 1540 | interfaceUpdate(true); 1541 | 1542 | var api = { 1543 | setImage: setImage, 1544 | animateTo: animateTo, 1545 | setSelect: setSelect, 1546 | setOptions: setOptionsNew, 1547 | tellSelect: tellSelect, 1548 | tellScaled: tellScaled, 1549 | setClass: setClass, 1550 | 1551 | disable: disableCrop, 1552 | enable: enableCrop, 1553 | cancel: cancelCrop, 1554 | release: Selection.release, 1555 | destroy: destroy, 1556 | 1557 | focus: KeyManager.watchKeys, 1558 | 1559 | getBounds: function () { 1560 | return [boundx * xscale, boundy * yscale]; 1561 | }, 1562 | getWidgetSize: function () { 1563 | return [boundx, boundy]; 1564 | }, 1565 | getScaleFactor: function () { 1566 | return [xscale, yscale]; 1567 | }, 1568 | getOptions: function() { 1569 | // careful: internal values are returned 1570 | return options; 1571 | }, 1572 | 1573 | ui: { 1574 | holder: $div, 1575 | selection: $sel 1576 | } 1577 | }; 1578 | 1579 | if (is_msie) $div.bind('selectstart', function () { return false; }); 1580 | 1581 | $origimg.data('Jcrop', api); 1582 | return api; 1583 | }; 1584 | $.fn.Jcrop = function (options, callback) //{{{ 1585 | { 1586 | var api; 1587 | // Iterate over each object, attach Jcrop 1588 | this.each(function () { 1589 | // If we've already attached to this object 1590 | if ($(this).data('Jcrop')) { 1591 | // The API can be requested this way (undocumented) 1592 | if (options === 'api') return $(this).data('Jcrop'); 1593 | // Otherwise, we just reset the options... 1594 | else $(this).data('Jcrop').setOptions(options); 1595 | } 1596 | // If we haven't been attached, preload and attach 1597 | else { 1598 | if (this.tagName == 'IMG') 1599 | $.Jcrop.Loader(this,function(){ 1600 | $(this).css({display:'block',visibility:'hidden'}); 1601 | api = $.Jcrop(this, options); 1602 | if ($.isFunction(callback)) callback.call(api); 1603 | }); 1604 | else { 1605 | $(this).css({display:'block',visibility:'hidden'}); 1606 | api = $.Jcrop(this, options); 1607 | if ($.isFunction(callback)) callback.call(api); 1608 | } 1609 | } 1610 | }); 1611 | 1612 | // Return "this" so the object is chainable (jQuery-style) 1613 | return this; 1614 | }; 1615 | //}}} 1616 | // $.Jcrop.Loader - basic image loader {{{ 1617 | 1618 | $.Jcrop.Loader = function(imgobj,success,error){ 1619 | var $img = $(imgobj), img = $img[0]; 1620 | 1621 | function completeCheck(){ 1622 | if (img.complete) { 1623 | $img.unbind('.jcloader'); 1624 | if ($.isFunction(success)) success.call(img); 1625 | } 1626 | else window.setTimeout(completeCheck,50); 1627 | } 1628 | 1629 | $img 1630 | .bind('load.jcloader',completeCheck) 1631 | .bind('error.jcloader',function(e){ 1632 | $img.unbind('.jcloader'); 1633 | if ($.isFunction(error)) error.call(img); 1634 | }); 1635 | 1636 | if (img.complete && $.isFunction(success)){ 1637 | $img.unbind('.jcloader'); 1638 | success.call(img); 1639 | } 1640 | }; 1641 | 1642 | //}}} 1643 | // Global Defaults {{{ 1644 | $.Jcrop.defaults = { 1645 | 1646 | // Basic Settings 1647 | allowSelect: true, 1648 | allowMove: true, 1649 | allowResize: true, 1650 | 1651 | trackDocument: true, 1652 | 1653 | // Styling Options 1654 | baseClass: 'jcrop', 1655 | addClass: null, 1656 | bgColor: 'black', 1657 | bgOpacity: 0.6, 1658 | bgFade: false, 1659 | borderOpacity: 0.4, 1660 | handleOpacity: 0.5, 1661 | handleSize: null, 1662 | 1663 | aspectRatio: 0, 1664 | keySupport: true, 1665 | createHandles: ['n','s','e','w','nw','ne','se','sw'], 1666 | createDragbars: ['n','s','e','w'], 1667 | createBorders: ['n','s','e','w'], 1668 | drawBorders: true, 1669 | dragEdges: true, 1670 | fixedSupport: true, 1671 | touchSupport: null, 1672 | 1673 | shade: null, 1674 | 1675 | boxWidth: 0, 1676 | boxHeight: 0, 1677 | boundary: 2, 1678 | fadeTime: 400, 1679 | animationDelay: 20, 1680 | swingSpeed: 3, 1681 | 1682 | minSelect: [0, 0], 1683 | maxSize: [0, 0], 1684 | minSize: [0, 0], 1685 | 1686 | // Callbacks / Event Handlers 1687 | onChange: function () {}, 1688 | onSelect: function () {}, 1689 | onDblClick: function () {}, 1690 | onRelease: function () {} 1691 | }; 1692 | 1693 | // }}} 1694 | }(jQuery)); 1695 | --------------------------------------------------------------------------------