Panda3D
imageFile.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file imageFile.cxx
10  * @author drose
11  * @date 2000-11-29
12  */
13 
14 #include "imageFile.h"
15 #include "palettizer.h"
16 #include "filenameUnifier.h"
17 #include "paletteGroup.h"
18 
19 #include "pnmImage.h"
20 #include "pnmFileType.h"
21 #include "eggTexture.h"
22 #include "datagram.h"
23 #include "datagramIterator.h"
24 #include "bamReader.h"
25 #include "bamWriter.h"
26 
27 using std::string;
28 
29 TypeHandle ImageFile::_type_handle;
30 
31 /**
32  *
33  */
34 ImageFile::
35 ImageFile() {
36  _alpha_file_channel = 0;
37  _size_known = false;
38  _x_size = 0;
39  _y_size = 0;
40 }
41 
42 /**
43  * Sets up the ImageFile as a "shadow image" of a particular PaletteImage.
44  * This is a temporary ImageFile that's used to read and write the shadow
45  * palette image, which is used to keep a working copy of the palette.
46  *
47  * Returns true if the filename changes from what it was previously, false
48  * otherwise.
49  */
51 make_shadow_image(const string &basename) {
52  bool any_changed = false;
53 
54  if (_properties._color_type != pal->_shadow_color_type ||
55  _properties._alpha_type != pal->_shadow_alpha_type) {
56 
57  _properties._color_type = pal->_shadow_color_type;
58  _properties._alpha_type = pal->_shadow_alpha_type;
59  any_changed = true;
60  }
61 
62  if (set_filename(pal->_shadow_dirname, basename)) {
63  any_changed = true;
64  }
65 
66  return any_changed;
67 }
68 
69 /**
70  * Returns true if the size of the image file is known, false otherwise.
71  */
73 is_size_known() const {
74  return _size_known;
75 }
76 
77 /**
78  * Returns the size of the image file in pixels in the X direction. It is an
79  * error to call this unless is_size_known() returns true.
80  */
82 get_x_size() const {
83  nassertr(is_size_known(), 0);
84  return _x_size;
85 }
86 
87 /**
88  * Returns the size of the image file in pixels in the Y direction. It is an
89  * error to call this unless is_size_known() returns true.
90  */
92 get_y_size() const {
93  nassertr(is_size_known(), 0);
94  return _y_size;
95 }
96 
97 /**
98  * Returns true if the number of channels in the image is known, false
99  * otherwise.
100  */
102 has_num_channels() const {
103  return _properties.has_num_channels();
104 }
105 
106 /**
107  * Returns the number of channels of the image. It is an error to call this
108  * unless has_num_channels() returns true.
109  */
111 get_num_channels() const {
112  return _properties.get_num_channels();
113 }
114 
115 /**
116  * Returns the grouping properties of the image.
117  */
119 get_properties() const {
120  return _properties;
121 }
122 
123 /**
124  * Resets the properties to a neutral state, for instance in preparation for
125  * calling update_properties() with all the known contributing properties.
126  */
129  _properties.clear_basic();
130 }
131 
132 /**
133  * If the indicate TextureProperties structure is more specific than this one,
134  * updates this one.
135  */
137 update_properties(const TextureProperties &properties) {
138  _properties.update_properties(properties);
139 }
140 
141 /**
142  * Sets the filename, and if applicable, the alpha_filename, from the
143  * indicated basename. The extension appropriate to the image file type
144  * specified in _color_type (and _alpha_type) is automatically applied.
145  *
146  * Returns true if the filename changes from what it was previously, false
147  * otherwise.
148  */
150 set_filename(PaletteGroup *group, const string &basename) {
151  // Synthesize the directory name based on the map_dirname set to the
152  // palettizer, and the group's dirname.
153  string dirname;
154  string::iterator pi;
155  pi = pal->_map_dirname.begin();
156  while (pi != pal->_map_dirname.end()) {
157  if (*pi == '%') {
158  ++pi;
159  switch (*pi) {
160  case '%':
161  dirname += '%';
162  break;
163 
164  case 'g':
165  if (group != nullptr) {
166  dirname += group->get_dirname();
167  }
168  break;
169  }
170  } else {
171  dirname += *pi;
172  }
173  ++pi;
174  }
175 
176  return set_filename(dirname, basename);
177 }
178 
179 /**
180  * Sets the filename, and if applicable, the alpha_filename, from the
181  * indicated basename. The extension appropriate to the image file type
182  * specified in _color_type (and _alpha_type) is automatically applied.
183  *
184  * Returns true if the filename changes from what it was previously, false
185  * otherwise.
186  */
188 set_filename(const string &dirname, const string &basename) {
189  Filename orig_filename = _filename;
190  Filename orig_alpha_filename = _alpha_filename;
191 
192  _filename = Filename(dirname, basename);
193  _filename.standardize();
194 
195  // Since we use set_extension() here, if the file already contains a
196  // filename extension it will be lost.
197 
198  // It is particularly important to note that a single embedded dot will
199  // appear to begin a filename extension, so if the filename does *not*
200  // contain an extension, but does contain an embedded dot, the filename will
201  // be truncated at that dot. It is therefore important that the supplied
202  // basename always contains either an extension or a terminating dot.
203 
204  if (_properties._color_type != nullptr) {
205  _filename.set_extension
206  (_properties._color_type->get_suggested_extension());
207  }
208 
209  if (_properties._alpha_type != nullptr) {
210  _alpha_filename = _filename.get_fullpath_wo_extension() + "_a.";
211  _alpha_filename.set_extension
212  (_properties._alpha_type->get_suggested_extension());
213  } else {
214  _alpha_filename = Filename();
215  }
216 
217  return (_filename != orig_filename ||
218  _alpha_filename != orig_alpha_filename);
219 }
220 
221 /**
222  * Returns the primary filename of the image file.
223  */
225 get_filename() const {
226  return _filename;
227 }
228 
229 /**
230  * Returns the alpha filename of the image file. This is the name of the file
231  * that contains the alpha channel, if it is stored in a separate file, or the
232  * empty string if it is not.
233  */
235 get_alpha_filename() const {
236  return _alpha_filename;
237 }
238 
239 /**
240  * Returns the particular channel number of the alpha image file from which
241  * the alpha channel should be extracted. This is normally 0 to represent the
242  * grayscale combination of r, g, and b; or it may be a 1-based channel number
243  * (for instance, 4 for the alpha channel of a 4-component image).
244  */
246 get_alpha_file_channel() const {
247  return _alpha_file_channel;
248 }
249 
250 
251 /**
252  * Returns true if the file or files named by the image file exist, false
253  * otherwise.
254  */
256 exists() const {
257  if (!_filename.exists()) {
258  return false;
259  }
260  if (_properties._alpha_type != nullptr &&
261  _properties.uses_alpha() &&
262  !_alpha_filename.empty()) {
263  if (!_alpha_filename.exists()) {
264  return false;
265  }
266  }
267 
268  return true;
269 }
270 
271 /**
272  * Reads in the image (or images, if the alpha_filename is separate) and
273  * stores it in the indicated PNMImage. Returns true on success, false on
274  * failure.
275  */
277 read(PNMImage &image) const {
278  nassertr(!_filename.empty(), false);
279 
280  image.set_type(_properties._color_type);
281  nout << "Reading " << FilenameUnifier::make_user_filename(_filename) << "\n";
282  if (!image.read(_filename)) {
283  nout << "Unable to read.\n";
284  return false;
285  }
286 
287  if (!_alpha_filename.empty() && _alpha_filename.exists()) {
288  // Read in a separate color image and an alpha channel image.
289  PNMImage alpha_image;
290  alpha_image.set_type(_properties._alpha_type);
291  nout << "Reading " << FilenameUnifier::make_user_filename(_alpha_filename) << "\n";
292  if (!alpha_image.read(_alpha_filename)) {
293  nout << "Unable to read.\n";
294  return false;
295  }
296  if (image.get_x_size() != alpha_image.get_x_size() ||
297  image.get_y_size() != alpha_image.get_y_size()) {
298  return false;
299  }
300 
301  image.add_alpha();
302 
303  if (_alpha_file_channel == 4 ||
304  (_alpha_file_channel == 2 && alpha_image.get_num_channels() == 2)) {
305  // Use the alpha channel.
306  for (int x = 0; x < image.get_x_size(); x++) {
307  for (int y = 0; y < image.get_y_size(); y++) {
308  image.set_alpha(x, y, alpha_image.get_alpha(x, y));
309  }
310  }
311 
312  } else if (_alpha_file_channel >= 1 && _alpha_file_channel <= 3 &&
313  alpha_image.get_num_channels() >= 3) {
314  // Use the appropriate red, green, or blue channel.
315  for (int x = 0; x < image.get_x_size(); x++) {
316  for (int y = 0; y < image.get_y_size(); y++) {
317  image.set_alpha(x, y, alpha_image.get_channel_val(x, y, _alpha_file_channel - 1));
318  }
319  }
320 
321  } else {
322  // Use the grayscale channel.
323  for (int x = 0; x < image.get_x_size(); x++) {
324  for (int y = 0; y < image.get_y_size(); y++) {
325  image.set_alpha(x, y, alpha_image.get_gray(x, y));
326  }
327  }
328  }
329  }
330 
331  return true;
332 }
333 
334 /**
335  * Writes out the image in the indicated PNMImage to the _filename and/or
336  * _alpha_filename. Returns true on success, false on failure.
337  */
339 write(const PNMImage &image) const {
340  nassertr(!_filename.empty(), false);
341 
342  if (!image.has_alpha() ||
343  _properties._alpha_type == nullptr) {
344  if (!_alpha_filename.empty() && _alpha_filename.exists()) {
345  nout << "Deleting " << FilenameUnifier::make_user_filename(_alpha_filename) << "\n";
346  _alpha_filename.unlink();
347  }
348  nout << "Writing " << FilenameUnifier::make_user_filename(_filename) << "\n";
349  _filename.make_dir();
350  if (!image.write(_filename, _properties._color_type)) {
351  nout << "Unable to write.\n";
352  return false;
353  }
354  return true;
355  }
356 
357  // Write out a separate color image and an alpha channel image.
358  PNMImage alpha_image(image.get_x_size(), image.get_y_size(), 1,
359  image.get_maxval());
360  for (int y = 0; y < image.get_y_size(); y++) {
361  for (int x = 0; x < image.get_x_size(); x++) {
362  alpha_image.set_gray_val(x, y, image.get_alpha_val(x, y));
363  }
364  }
365 
366  PNMImage image_copy(image);
367  image_copy.remove_alpha();
368  nout << "Writing " << FilenameUnifier::make_user_filename(_filename) << "\n";
369  _filename.make_dir();
370  if (!image_copy.write(_filename, _properties._color_type)) {
371  nout << "Unable to write.\n";
372  return false;
373  }
374 
375  nout << "Writing " << FilenameUnifier::make_user_filename(_alpha_filename) << "\n";
376  _alpha_filename.make_dir();
377  if (!alpha_image.write(_alpha_filename, _properties._alpha_type)) {
378  nout << "Unable to write.\n";
379  return false;
380  }
381  return true;
382 }
383 
384 /**
385  * Deletes the image file or files.
386  */
388 unlink() {
389  if (!_filename.empty() && _filename.exists()) {
390  nout << "Deleting " << FilenameUnifier::make_user_filename(_filename) << "\n";
391  _filename.unlink();
392  }
393  if (!_alpha_filename.empty() && _alpha_filename.exists()) {
394  nout << "Deleting " << FilenameUnifier::make_user_filename(_alpha_filename) << "\n";
395  _alpha_filename.unlink();
396  }
397 }
398 
399 /**
400  * Sets the indicated EggTexture to refer to this file.
401  */
403 update_egg_tex(EggTexture *egg_tex) const {
404  nassertv(egg_tex != nullptr);
405 
406  egg_tex->set_filename(FilenameUnifier::make_egg_filename(_filename));
407 
408  if (_properties.uses_alpha() &&
409  !_alpha_filename.empty()) {
410  egg_tex->set_alpha_filename(FilenameUnifier::make_egg_filename(_alpha_filename));
411  egg_tex->set_alpha_file_channel(_alpha_file_channel);
412  } else {
413  egg_tex->clear_alpha_filename();
414  egg_tex->clear_alpha_file_channel();
415  }
416 
417  _properties.update_egg_tex(egg_tex);
418 }
419 
420 /**
421  * Writes the filename (or pair of filenames) to the indicated output stream.
422  */
424 output_filename(std::ostream &out) const {
425  out << FilenameUnifier::make_user_filename(_filename);
426  if (_properties.uses_alpha() && !_alpha_filename.empty()) {
427  out << " " << FilenameUnifier::make_user_filename(_alpha_filename);
428  }
429 }
430 
431 /**
432  * Fills the indicated datagram up with a binary representation of the current
433  * object, in preparation for writing to a Bam file.
434  */
436 write_datagram(BamWriter *writer, Datagram &datagram) {
437  TypedWritable::write_datagram(writer, datagram);
438  _properties.write_datagram(writer, datagram);
439  datagram.add_string(FilenameUnifier::make_bam_filename(_filename));
440  datagram.add_string(FilenameUnifier::make_bam_filename(_alpha_filename));
441  datagram.add_uint8(_alpha_file_channel);
442  datagram.add_bool(_size_known);
443  datagram.add_int32(_x_size);
444  datagram.add_int32(_y_size);
445 }
446 
447 /**
448  * Called after the object is otherwise completely read from a Bam file, this
449  * function's job is to store the pointers that were retrieved from the Bam
450  * file for each pointer object written. The return value is the number of
451  * pointers processed from the list.
452  */
454 complete_pointers(TypedWritable **p_list, BamReader *manager) {
455  int pi = TypedWritable::complete_pointers(p_list, manager);
456 
457  pi += _properties.complete_pointers(p_list + pi, manager);
458 
459  return pi;
460 }
461 
462 /**
463  * Reads the binary data from the given datagram iterator, which was written
464  * by a previous call to write_datagram().
465  */
466 void ImageFile::
467 fillin(DatagramIterator &scan, BamReader *manager) {
468  TypedWritable::fillin(scan, manager);
469  _properties.fillin(scan, manager);
470  _filename = FilenameUnifier::get_bam_filename(scan.get_string());
471  _alpha_filename = FilenameUnifier::get_bam_filename(scan.get_string());
472  if (Palettizer::_read_pi_version >= 10) {
473  _alpha_file_channel = scan.get_uint8();
474  } else {
475  _alpha_file_channel = 0;
476  }
477  _size_known = scan.get_bool();
478  _x_size = scan.get_int32();
479  _y_size = scan.get_int32();
480 }
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition: bamReader.h:110
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition: bamWriter.h:63
A class to retrieve the individual data elements previously stored in a Datagram.
uint8_t get_uint8()
Extracts an unsigned 8-bit integer.
bool get_bool()
Extracts a boolean value.
std::string get_string()
Extracts a variable-length string.
int32_t get_int32()
Extracts a signed 32-bit integer.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition: datagram.h:38
void add_int32(int32_t value)
Adds a signed 32-bit integer to the datagram.
Definition: datagram.I:67
void add_uint8(uint8_t value)
Adds an unsigned 8-bit integer to the datagram.
Definition: datagram.I:50
void add_bool(bool value)
Adds a boolean value to the datagram.
Definition: datagram.I:34
void add_string(const std::string &str)
Adds a variable-length string to the datagram.
Definition: datagram.I:219
Defines a texture map that may be applied to geometry.
Definition: eggTexture.h:30
set_alpha_file_channel
If a separate alpha-file is specified, this indicates which channel number should be extracted from t...
Definition: eggTexture.h:350
clear_alpha_file_channel
Removes the specification of a particular channel to use from the alpha- file image.
Definition: eggTexture.h:350
set_alpha_filename
Specifies a separate file that will be loaded in with the 1- or 3-component texture and applied as th...
Definition: eggTexture.h:347
static Filename get_bam_filename(Filename filename)
Returns an absolute pathname based on the given relative pathname, presumably read from the bam file ...
static Filename make_bam_filename(Filename filename)
Returns a new filename that's made relative to the bam file itself, suitable for writing to the bam f...
static Filename make_user_filename(Filename filename)
Returns a new filename that's made relative to the current directory, suitable for reporting to the u...
static Filename make_egg_filename(Filename filename)
Returns a new filename that's made relative to the rel_directory, suitable for writing out within egg...
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
std::string get_fullpath_wo_extension() const
Returns the full filename–directory and basename parts–except for the extension.
Definition: filename.I:377
bool unlink() const
Permanently deletes the file associated with the filename, if possible.
Definition: filename.cxx:2319
void standardize()
Converts the filename to standard form by replacing consecutive slashes with a single slash,...
Definition: filename.cxx:900
bool make_dir() const
Creates all the directories in the path to the file specified in the filename, except for the basenam...
Definition: filename.cxx:2484
void set_extension(const std::string &s)
Replaces the file extension.
Definition: filename.cxx:804
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1267
void update_egg_tex(EggTexture *egg_tex) const
Sets the indicated EggTexture to refer to this file.
Definition: imageFile.cxx:403
int get_num_channels() const
Returns the number of channels of the image.
Definition: imageFile.cxx:111
bool write(const PNMImage &image) const
Writes out the image in the indicated PNMImage to the _filename and/or _alpha_filename.
Definition: imageFile.cxx:339
bool set_filename(PaletteGroup *group, const std::string &basename)
Sets the filename, and if applicable, the alpha_filename, from the indicated basename.
Definition: imageFile.cxx:150
void unlink()
Deletes the image file or files.
Definition: imageFile.cxx:388
int get_x_size() const
Returns the size of the image file in pixels in the X direction.
Definition: imageFile.cxx:82
void output_filename(std::ostream &out) const
Writes the filename (or pair of filenames) to the indicated output stream.
Definition: imageFile.cxx:424
bool exists() const
Returns true if the file or files named by the image file exist, false otherwise.
Definition: imageFile.cxx:256
virtual int complete_pointers(TypedWritable **p_list, BamReader *manager)
Called after the object is otherwise completely read from a Bam file, this function's job is to store...
Definition: imageFile.cxx:454
bool make_shadow_image(const std::string &basename)
Sets up the ImageFile as a "shadow image" of a particular PaletteImage.
Definition: imageFile.cxx:51
const TextureProperties & get_properties() const
Returns the grouping properties of the image.
Definition: imageFile.cxx:119
const Filename & get_filename() const
Returns the primary filename of the image file.
Definition: imageFile.cxx:225
bool read(PNMImage &image) const
Reads in the image (or images, if the alpha_filename is separate) and stores it in the indicated PNMI...
Definition: imageFile.cxx:277
const Filename & get_alpha_filename() const
Returns the alpha filename of the image file.
Definition: imageFile.cxx:235
virtual void write_datagram(BamWriter *writer, Datagram &datagram)
Fills the indicated datagram up with a binary representation of the current object,...
Definition: imageFile.cxx:436
bool is_size_known() const
Returns true if the size of the image file is known, false otherwise.
Definition: imageFile.cxx:73
void update_properties(const TextureProperties &properties)
If the indicate TextureProperties structure is more specific than this one, updates this one.
Definition: imageFile.cxx:137
int get_y_size() const
Returns the size of the image file in pixels in the Y direction.
Definition: imageFile.cxx:92
int get_alpha_file_channel() const
Returns the particular channel number of the alpha image file from which the alpha channel should be ...
Definition: imageFile.cxx:246
bool has_num_channels() const
Returns true if the number of channels in the image is known, false otherwise.
Definition: imageFile.cxx:102
void clear_basic_properties()
Resets the properties to a neutral state, for instance in preparation for calling update_properties()...
Definition: imageFile.cxx:128
get_suggested_extension
Returns a suitable filename extension (without a leading dot) to suggest for files of this type,...
Definition: pnmFileType.h:49
get_maxval
Returns the maximum channel value allowable for any pixel in this image; for instance,...
int get_x_size() const
Returns the number of pixels in the X direction.
get_num_channels
Returns the number of channels in the image.
static bool has_alpha(ColorType color_type)
This static variant of has_alpha() returns true if the indicated image type includes an alpha channel...
int get_y_size() const
Returns the number of pixels in the Y direction.
void set_type(PNMFileType *type)
Sets the file type of this PNMImage.
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:58
xelval get_channel_val(int x, int y, int channel) const
Returns the nth component color at the indicated pixel.
Definition: pnmImage.cxx:837
float get_alpha(int x, int y) const
Returns the alpha component color at the indicated pixel.
Definition: pnmImage.I:809
float get_gray(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition: pnmImage.I:799
void remove_alpha()
Removes the image's alpha channel, if it exists.
Definition: pnmImage.I:371
void set_gray_val(int x, int y, xelval gray)
Sets the gray component color at the indicated pixel.
Definition: pnmImage.I:545
xelval get_alpha_val(int x, int y) const
Returns the alpha component color at the indicated pixel.
Definition: pnmImage.I:494
bool read(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true)
Reads the indicated image filename.
Definition: pnmImage.cxx:278
void set_alpha(int x, int y, float a)
Sets the alpha component color only at the indicated pixel.
Definition: pnmImage.I:859
void add_alpha()
Adds an alpha channel to the image, if it does not already have one.
Definition: pnmImage.I:363
bool write(const Filename &filename, PNMFileType *type=nullptr) const
Writes the image to the indicated filename.
Definition: pnmImage.cxx:385
This is the highest level of grouping for TextureImages.
Definition: paletteGroup.h:43
const std::string & get_dirname() const
Returns the directory name associated with the palette group.
This is the set of characteristics of a texture that, if different from another texture,...
void update_egg_tex(EggTexture *egg_tex) const
Adjusts the texture properties of the indicated egg reference to match these properties.
int get_num_channels() const
Returns the number of channels (1 through 4) associated with the image.
void clear_basic()
Resets only the properties that might be changed by update_properties() to a neutral state.
void update_properties(const TextureProperties &other)
If the indicate TextureProperties structure is more specific than this one, updates this one.
virtual int complete_pointers(TypedWritable **p_list, BamReader *manager)
Called after the object is otherwise completely read from a Bam file, this function's job is to store...
void fillin(DatagramIterator &scan, BamReader *manager)
Reads the binary data from the given datagram iterator, which was written by a previous call to write...
virtual void write_datagram(BamWriter *writer, Datagram &datagram)
Fills the indicated datagram up with a binary representation of the current object,...
bool has_num_channels() const
Returns true if the number of channels is known.
bool uses_alpha() const
Returns true if the texture uses an alpha channel, false otherwise.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:35
virtual void fillin(DatagramIterator &scan, BamReader *manager)
This internal function is intended to be called by each class's make_from_bam() method to read in all...
virtual void write_datagram(BamWriter *manager, Datagram &dg)
Writes the contents of this object to the datagram for shipping out to a Bam file.
virtual int complete_pointers(TypedWritable **p_list, BamReader *manager)
Receives an array of pointers, one for each time manager->read_pointer() was called in fillin().
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.